diff --git a/.github/actions/install-deb/action.yml b/.github/actions/install-deb/action.yml new file mode 100644 index 00000000000..c33bfb220ba --- /dev/null +++ b/.github/actions/install-deb/action.yml @@ -0,0 +1,35 @@ +name: Install deb +description: Download and install a .deb package +inputs: + url: + description: URL to the .deb file + required: true + name: + description: A local name for the package. Required if using this action multiple times in the same context. + default: package.deb + required: false + +runs: + using: 'composite' + steps: + - name: Restore deb + id: deb-restore + uses: actions/cache/restore@v4 + with: + path: "${{ runner.temp }}/${{ inputs.name }}" + key: ${{ inputs.url }} + - name: Download deb + if: ${{ steps.deb-restore.outputs.cache-hit != 'true' }} + shell: bash + run: | + wget --no-verbose "${{ inputs.url }}" -O "${{ runner.temp }}/${{ inputs.name }}" + - name: Cache deb + if: ${{ steps.deb-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v4 + with: + path: "${{ runner.temp }}/${{ inputs.name }}" + key: ${{ inputs.url }} + - name: Install deb + shell: bash + run: | + sudo apt-get install -y --allow-downgrades "${{ runner.temp }}/${{ inputs.name }}" diff --git a/.github/actions/load/action.yml b/.github/actions/load/action.yml index 0102608dbd1..e3fc00dc6ae 100644 --- a/.github/actions/load/action.yml +++ b/.github/actions/load/action.yml @@ -24,10 +24,14 @@ runs: rm -r "$(pwd)"/* - name: Download artifact - uses: actions/download-artifact@v5 + uses: Wandalen/wretry.action@v3.8.0 with: - path: '${{ runner.temp }}' - name: '${{ inputs.name }}' + action: actions/download-artifact@v7 + attempt_limit: 2 + attempt_delay: 10000 + with: | + path: '${{ runner.temp }}' + name: '${{ inputs.name }}' - name: 'Untar working directory' shell: bash diff --git a/.github/actions/unzip-artifact/action.yml b/.github/actions/unzip-artifact/action.yml new file mode 100644 index 00000000000..a05c15a00cd --- /dev/null +++ b/.github/actions/unzip-artifact/action.yml @@ -0,0 +1,52 @@ +name: Unzip artifact +description: Download and unzip artifact from a triggering workflow +inputs: + name: + description: Artifact name +outputs: + exists: + description: true if the artifact was found + value: ${{ steps.download.outputs.result }} + +runs: + using: 'composite' + steps: + - name: 'Delay waiting for artifacts to be ready' + shell: bash + run: sleep 10 + - name: 'Download artifact' + id: download + uses: actions/github-script@v8 + with: + result-encoding: string + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "${{ inputs.name }}" + })[0]; + if (matchArtifact == null) { + return "false" + } + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + const fs = require('fs'); + const path = require('path'); + const temp = '${{ runner.temp }}/artifacts'; + if (!fs.existsSync(temp)){ + fs.mkdirSync(temp); + } + fs.writeFileSync(path.join(temp, 'artifact.zip'), Buffer.from(download.data)); + return "true"; + + - name: 'Unzip artifact' + shell: bash + if: ${{ steps.download.outputs.result == 'true' }} + run: unzip "${{ runner.temp }}/artifacts/artifact.zip" -d "${{ runner.temp }}/artifacts" diff --git a/.github/actions/wait-for-browserstack/action.yml b/.github/actions/wait-for-browserstack/action.yml index 12ad89d7008..3242e29fc50 100644 --- a/.github/actions/wait-for-browserstack/action.yml +++ b/.github/actions/wait-for-browserstack/action.yml @@ -1,6 +1,9 @@ name: Wait for browserstack sessions description: Wait until enough browserstack sessions have become available - +inputs: + sessions: + description: Number of sessions needed to continue + default: "6" runs: using: 'composite' steps: @@ -14,7 +17,7 @@ runs: queued=$(jq '.queued_sessions' <<< $status) max_queued=$(jq '.queued_sessions_max_allowed' <<< $status) spare=$(( ${max_running} + ${max_queued} - ${running} - ${queued} )) - required=6 + required=${{ inputs.sessions }} echo "Browserstack status: ${running} sessions running, ${queued} queued, ${spare} free" (( ${required} > ${spare} )) do diff --git a/.github/codeql/queries/autogen_fpDOMMethod.qll b/.github/codeql/queries/autogen_fpDOMMethod.qll index 7e21d791a69..164a699e97b 100644 --- a/.github/codeql/queries/autogen_fpDOMMethod.qll +++ b/.github/codeql/queries/autogen_fpDOMMethod.qll @@ -7,9 +7,9 @@ class DOMMethod extends string { DOMMethod() { - ( this = "toDataURL" and weight = 25.89 and type = "HTMLCanvasElement" ) + ( this = "toDataURL" and weight = 32.64 and type = "HTMLCanvasElement" ) or - ( this = "getChannelData" and weight = 806.52 and type = "AudioBuffer" ) + ( this = "getChannelData" and weight = 1009.41 and type = "AudioBuffer" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpEventProperty.qll b/.github/codeql/queries/autogen_fpEventProperty.qll index d136c7a6ab6..25ecd018f0f 100644 --- a/.github/codeql/queries/autogen_fpEventProperty.qll +++ b/.github/codeql/queries/autogen_fpEventProperty.qll @@ -7,21 +7,21 @@ class EventProperty extends string { EventProperty() { - ( this = "accelerationIncludingGravity" and weight = 158.1 and event = "devicemotion" ) + ( this = "candidate" and weight = 54.73 and event = "icecandidate" ) or - ( this = "beta" and weight = 887.22 and event = "deviceorientation" ) + ( this = "rotationRate" and weight = 63.55 and event = "devicemotion" ) or - ( this = "gamma" and weight = 361.7 and event = "deviceorientation" ) + ( this = "accelerationIncludingGravity" and weight = 205.08 and event = "devicemotion" ) or - ( this = "alpha" and weight = 354.09 and event = "deviceorientation" ) + ( this = "acceleration" and weight = 64.53 and event = "devicemotion" ) or - ( this = "candidate" and weight = 69.81 and event = "icecandidate" ) + ( this = "alpha" and weight = 784.67 and event = "deviceorientation" ) or - ( this = "acceleration" and weight = 64.92 and event = "devicemotion" ) + ( this = "beta" and weight = 801.42 and event = "deviceorientation" ) or - ( this = "rotationRate" and weight = 64.37 and event = "devicemotion" ) + ( this = "gamma" and weight = 300.01 and event = "deviceorientation" ) or - ( this = "absolute" and weight = 709.73 and event = "deviceorientation" ) + ( this = "absolute" and weight = 281.45 and event = "deviceorientation" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalConstructor.qll b/.github/codeql/queries/autogen_fpGlobalConstructor.qll index 43213748fa3..8feceaae940 100644 --- a/.github/codeql/queries/autogen_fpGlobalConstructor.qll +++ b/.github/codeql/queries/autogen_fpGlobalConstructor.qll @@ -6,15 +6,15 @@ class GlobalConstructor extends string { GlobalConstructor() { - ( this = "OfflineAudioContext" and weight = 1111.66 ) + ( this = "SharedWorker" and weight = 74.12 ) or - ( this = "SharedWorker" and weight = 93.35 ) + ( this = "OfflineAudioContext" and weight = 1062.83 ) or - ( this = "RTCPeerConnection" and weight = 49.52 ) + ( this = "RTCPeerConnection" and weight = 36.17 ) or - ( this = "Gyroscope" and weight = 98.72 ) + ( this = "Gyroscope" and weight = 100.27 ) or - ( this = "AudioWorkletNode" and weight = 72.93 ) + ( this = "AudioWorkletNode" and weight = 145.12 ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll b/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll index bd815ca8cce..19489a50149 100644 --- a/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll +++ b/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll @@ -7,57 +7,57 @@ class GlobalObjectProperty0 extends string { GlobalObjectProperty0() { - ( this = "cookieEnabled" and weight = 15.36 and global0 = "navigator" ) + ( this = "availHeight" and weight = 65.33 and global0 = "screen" ) or - ( this = "availHeight" and weight = 69.48 and global0 = "screen" ) + ( this = "availWidth" and weight = 61.95 and global0 = "screen" ) or - ( this = "availWidth" and weight = 65.15 and global0 = "screen" ) + ( this = "colorDepth" and weight = 38.5 and global0 = "screen" ) or - ( this = "colorDepth" and weight = 34.39 and global0 = "screen" ) + ( this = "availTop" and weight = 1305.37 and global0 = "screen" ) or - ( this = "deviceMemory" and weight = 75.15 and global0 = "navigator" ) + ( this = "plugins" and weight = 15.16 and global0 = "navigator" ) or - ( this = "availTop" and weight = 1256.76 and global0 = "screen" ) + ( this = "deviceMemory" and weight = 64.15 and global0 = "navigator" ) or - ( this = "getBattery" and weight = 124.12 and global0 = "navigator" ) + ( this = "getBattery" and weight = 41.16 and global0 = "navigator" ) or - ( this = "webdriver" and weight = 30.18 and global0 = "navigator" ) + ( this = "webdriver" and weight = 27.64 and global0 = "navigator" ) or - ( this = "permission" and weight = 22.23 and global0 = "Notification" ) + ( this = "permission" and weight = 24.67 and global0 = "Notification" ) or - ( this = "storage" and weight = 170.65 and global0 = "navigator" ) + ( this = "storage" and weight = 35.77 and global0 = "navigator" ) or - ( this = "orientation" and weight = 38.3 and global0 = "screen" ) + ( this = "onLine" and weight = 18.84 and global0 = "navigator" ) or - ( this = "onLine" and weight = 20.05 and global0 = "navigator" ) + ( this = "pixelDepth" and weight = 45.77 and global0 = "screen" ) or - ( this = "pixelDepth" and weight = 38.22 and global0 = "screen" ) + ( this = "availLeft" and weight = 624.44 and global0 = "screen" ) or - ( this = "availLeft" and weight = 539.55 and global0 = "screen" ) + ( this = "orientation" and weight = 34.16 and global0 = "screen" ) or - ( this = "vendorSub" and weight = 1462.45 and global0 = "navigator" ) + ( this = "vendorSub" and weight = 1873.27 and global0 = "navigator" ) or - ( this = "productSub" and weight = 525.88 and global0 = "navigator" ) + ( this = "productSub" and weight = 381.87 and global0 = "navigator" ) or - ( this = "webkitTemporaryStorage" and weight = 40.85 and global0 = "navigator" ) + ( this = "webkitTemporaryStorage" and weight = 37.97 and global0 = "navigator" ) or - ( this = "hardwareConcurrency" and weight = 70.43 and global0 = "navigator" ) + ( this = "hardwareConcurrency" and weight = 51.78 and global0 = "navigator" ) or - ( this = "appCodeName" and weight = 152.93 and global0 = "navigator" ) + ( this = "appCodeName" and weight = 173.35 and global0 = "navigator" ) or - ( this = "keyboard" and weight = 2426.5 and global0 = "navigator" ) + ( this = "keyboard" and weight = 1722.82 and global0 = "navigator" ) or - ( this = "mediaDevices" and weight = 123.07 and global0 = "navigator" ) + ( this = "mediaDevices" and weight = 149.07 and global0 = "navigator" ) or - ( this = "mediaCapabilities" and weight = 124.39 and global0 = "navigator" ) + ( this = "mediaCapabilities" and weight = 142.34 and global0 = "navigator" ) or - ( this = "permissions" and weight = 70.22 and global0 = "navigator" ) + ( this = "permissions" and weight = 89.71 and global0 = "navigator" ) or - ( this = "webkitPersistentStorage" and weight = 113.71 and global0 = "navigator" ) + ( this = "webkitPersistentStorage" and weight = 134.12 and global0 = "navigator" ) or - ( this = "requestMediaKeySystemAccess" and weight = 16.88 and global0 = "navigator" ) + ( this = "requestMediaKeySystemAccess" and weight = 18.22 and global0 = "navigator" ) or - ( this = "getGamepads" and weight = 202.54 and global0 = "navigator" ) + ( this = "getGamepads" and weight = 209.55 and global0 = "navigator" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll b/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll index b874d860835..4ba664c998f 100644 --- a/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll +++ b/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll @@ -8,7 +8,7 @@ class GlobalObjectProperty1 extends string { GlobalObjectProperty1() { - ( this = "enumerateDevices" and weight = 329.65 and global0 = "navigator" and global1 = "mediaDevices" ) + ( this = "enumerateDevices" and weight = 595.56 and global0 = "navigator" and global1 = "mediaDevices" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll b/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll index df96f92eaed..b26e3689251 100644 --- a/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll +++ b/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll @@ -7,11 +7,11 @@ class GlobalTypeProperty0 extends string { GlobalTypeProperty0() { - ( this = "x" and weight = 7033.93 and global0 = "Gyroscope" ) + ( this = "x" and weight = 4255.55 and global0 = "Gyroscope" ) or - ( this = "y" and weight = 7033.93 and global0 = "Gyroscope" ) + ( this = "y" and weight = 4255.55 and global0 = "Gyroscope" ) or - ( this = "z" and weight = 7033.93 and global0 = "Gyroscope" ) + ( this = "z" and weight = 4255.55 and global0 = "Gyroscope" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll b/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll index dbdf06d0d47..084e91305b6 100644 --- a/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll +++ b/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll @@ -8,7 +8,7 @@ class GlobalTypeProperty1 extends string { GlobalTypeProperty1() { - ( this = "resolvedOptions" and weight = 18.83 and global0 = "Intl" and global1 = "DateTimeFormat" ) + ( this = "resolvedOptions" and weight = 19.01 and global0 = "Intl" and global1 = "DateTimeFormat" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalVar.qll b/.github/codeql/queries/autogen_fpGlobalVar.qll index 7a337b3519d..a28f1c7772c 100644 --- a/.github/codeql/queries/autogen_fpGlobalVar.qll +++ b/.github/codeql/queries/autogen_fpGlobalVar.qll @@ -6,23 +6,23 @@ class GlobalVar extends string { GlobalVar() { - ( this = "devicePixelRatio" and weight = 18.91 ) + ( this = "devicePixelRatio" and weight = 18.39 ) or - ( this = "screenX" and weight = 355.18 ) + ( this = "screenX" and weight = 366.36 ) or - ( this = "screenY" and weight = 309.2 ) + ( this = "screenY" and weight = 320.66 ) or - ( this = "outerWidth" and weight = 109.86 ) + ( this = "outerWidth" and weight = 104.67 ) or - ( this = "outerHeight" and weight = 178.05 ) + ( this = "outerHeight" and weight = 154.1 ) or - ( this = "screenLeft" and weight = 374.27 ) + ( this = "screenLeft" and weight = 321.49 ) or - ( this = "screenTop" and weight = 373.73 ) + ( this = "screenTop" and weight = 322.32 ) or - ( this = "indexedDB" and weight = 18.81 ) + ( this = "indexedDB" and weight = 23.36 ) or - ( this = "openDatabase" and weight = 134.7 ) + ( this = "openDatabase" and weight = 146.11 ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpRenderingContextProperty.qll b/.github/codeql/queries/autogen_fpRenderingContextProperty.qll index 510e393b984..e508d42520b 100644 --- a/.github/codeql/queries/autogen_fpRenderingContextProperty.qll +++ b/.github/codeql/queries/autogen_fpRenderingContextProperty.qll @@ -7,35 +7,35 @@ class RenderingContextProperty extends string { RenderingContextProperty() { - ( this = "getExtension" and weight = 17.76 and contextType = "webgl" ) + ( this = "getExtension" and weight = 24.59 and contextType = "webgl" ) or - ( this = "getParameter" and weight = 20.31 and contextType = "webgl" ) + ( this = "getParameter" and weight = 28.11 and contextType = "webgl" ) or - ( this = "getParameter" and weight = 65.17 and contextType = "webgl2" ) + ( this = "getImageData" and weight = 62.25 and contextType = "2d" ) or - ( this = "getShaderPrecisionFormat" and weight = 107.03 and contextType = "webgl2" ) + ( this = "measureText" and weight = 43.06 and contextType = "2d" ) or - ( this = "getExtension" and weight = 70.03 and contextType = "webgl2" ) + ( this = "getParameter" and weight = 67.61 and contextType = "webgl2" ) or - ( this = "getContextAttributes" and weight = 175.38 and contextType = "webgl2" ) + ( this = "getShaderPrecisionFormat" and weight = 138.74 and contextType = "webgl2" ) or - ( this = "getSupportedExtensions" and weight = 487.31 and contextType = "webgl2" ) + ( this = "getExtension" and weight = 69.66 and contextType = "webgl2" ) or - ( this = "getImageData" and weight = 44.3 and contextType = "2d" ) + ( this = "getContextAttributes" and weight = 201.04 and contextType = "webgl2" ) or - ( this = "measureText" and weight = 47.23 and contextType = "2d" ) + ( this = "getSupportedExtensions" and weight = 360.36 and contextType = "webgl2" ) or - ( this = "getShaderPrecisionFormat" and weight = 595.72 and contextType = "webgl" ) + ( this = "readPixels" and weight = 24.33 and contextType = "webgl" ) or - ( this = "getContextAttributes" and weight = 1038.26 and contextType = "webgl" ) + ( this = "getShaderPrecisionFormat" and weight = 1347.35 and contextType = "webgl" ) or - ( this = "getSupportedExtensions" and weight = 805.83 and contextType = "webgl" ) + ( this = "getContextAttributes" and weight = 2411.38 and contextType = "webgl" ) or - ( this = "readPixels" and weight = 20.6 and contextType = "webgl" ) + ( this = "getSupportedExtensions" and weight = 1484.82 and contextType = "webgl" ) or - ( this = "isPointInPath" and weight = 7033.93 and contextType = "2d" ) + ( this = "isPointInPath" and weight = 4255.55 and contextType = "2d" ) or - ( this = "readPixels" and weight = 73.62 and contextType = "webgl2" ) + ( this = "readPixels" and weight = 1004.16 and contextType = "webgl2" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpSensorProperty.qll b/.github/codeql/queries/autogen_fpSensorProperty.qll index 776a78d8434..bfc5c329068 100644 --- a/.github/codeql/queries/autogen_fpSensorProperty.qll +++ b/.github/codeql/queries/autogen_fpSensorProperty.qll @@ -6,7 +6,7 @@ class SensorProperty extends string { SensorProperty() { - ( this = "start" and weight = 104.06 ) + ( this = "start" and weight = 105.54 ) } float getWeight() { diff --git a/.github/codeql/queries/jsonRequestContentType.ql b/.github/codeql/queries/jsonRequestContentType.ql index b0ec95850ff..dbb8586a60c 100644 --- a/.github/codeql/queries/jsonRequestContentType.ql +++ b/.github/codeql/queries/jsonRequestContentType.ql @@ -12,7 +12,8 @@ from Property prop where prop.getName() = "contentType" and prop.getInit() instanceof StringLiteral and - prop.getInit().(StringLiteral).getStringValue() = "application/json" + prop.getInit().(StringLiteral).getStringValue() = "application/json" and + prop.getFile().getBaseName().matches("%BidAdapter.%") select prop, "application/json request type triggers preflight requests and may increase bidder timeouts" diff --git a/.github/workflows/PR-assignment-deps.yml b/.github/workflows/PR-assignment-deps.yml new file mode 100644 index 00000000000..2d7fce88837 --- /dev/null +++ b/.github/workflows/PR-assignment-deps.yml @@ -0,0 +1,34 @@ +name: Compile dependencies.json for PR assignment checks +on: + pull_request: + types: [opened, synchronize, reopened] +permissions: + contents: read +jobs: + generate_deps: + name: Generate dependencies.json + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: refs/pull/${{ github.event.pull_request.number }}/head + - name: Install dependencies + uses: ./.github/actions/npm-ci + - name: Build + run: | + npx gulp build + - name: Upload dependencies.json + uses: actions/upload-artifact@v7 + with: + name: dependencies.json + path: ./build/dist/dependencies.json + - name: Generate PR info + run: | + echo '{ "prNo": ${{ github.event.pull_request.number }} }' >> ${{ runner.temp}}/prInfo.json + - name: Upload PR info + uses: actions/upload-artifact@v7 + with: + name: prInfo + path: ${{ runner.temp}}/prInfo.json diff --git a/.github/workflows/PR-assignment.yml b/.github/workflows/PR-assignment.yml index 1ae8a1ae249..1a40f30abc0 100644 --- a/.github/workflows/PR-assignment.yml +++ b/.github/workflows/PR-assignment.yml @@ -1,31 +1,35 @@ name: Assign PR reviewers on: - pull_request_target: - types: [opened, synchronize, reopened] - + workflow_run: + workflows: + - Compile dependencies.json for PR assignment checks + types: + - completed jobs: assign_reviewers: + if: ${{ github.event.workflow_run.conclusion == 'success' }} name: Assign reviewers runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: master - name: Generate app token id: token uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PR_BOT_ID }} private-key: ${{ secrets.PR_BOT_PEM }} - - - name: Checkout - uses: actions/checkout@v5 + - name: Download dependencies.json + uses: ./.github/actions/unzip-artifact with: - ref: refs/pull/${{ github.event.pull_request.number }}/head - - - name: Install dependencies - uses: ./.github/actions/npm-ci - - name: Build - run: | - npx gulp build + name: dependencies.json + - name: Download PR info + uses: ./.github/actions/unzip-artifact + with: + name: prInfo - name: Install s3 client run: | npm install @aws-sdk/client-s3 @@ -35,16 +39,20 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ vars.PR_BOT_AWS_AK }} AWS_SECRET_ACCESS_KEY: ${{ secrets.PR_BOT_AWS_SAK }} + DEPENDENCIES_JSON: ${{ runner.temp }}/artifacts/dependencies.json with: github-token: ${{ steps.token.outputs.token }} script: | + const fs = require('fs'); const getProps = require('./.github/workflows/scripts/getPRProperties.js') + const { prNo } = JSON.parse(fs.readFileSync('${{runner.temp}}/artifacts/prInfo.json').toString()); const props = await getProps({ github, context, - prNo: ${{ github.event.pull_request.number }}, + prNo, reviewerTeam: '${{ vars.REVIEWER_TEAM }}', - engTeam: '${{ vars.ENG_TEAM }}' + engTeam: '${{ vars.ENG_TEAM }}', + authReviewTeam: '${{ vars.AUTH_REVIEWER_TEAM }}' }); console.log('PR properties:', JSON.stringify(props, null, 2)); return props; diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml index 8dfc9dcf665..65c7d8d6540 100644 --- a/.github/workflows/browser-tests.yml +++ b/.github/workflows/browser-tests.yml @@ -18,14 +18,16 @@ jobs: setup: needs: build - name: "Setup environment" + name: "Define testing strategy" runs-on: ubuntu-latest outputs: browsers: ${{ toJSON(fromJSON(steps.define.outputs.result).browsers) }} + latestBrowsers: ${{ toJSON(fromJSON(steps.define.outputs.result).latestBrowsers) }} bstack-key: ${{ steps.bstack-save.outputs.name }} + bstack-sessions: ${{ fromJSON(steps.define.outputs.result).bsBrowsers }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore working directory uses: ./.github/actions/load with: @@ -33,26 +35,48 @@ jobs: - name: "Define testing strategy" uses: actions/github-script@v8 id: define + env: + browserstack: ${{ secrets.BROWSERSTACK_USER_NAME }} with: script: | const fs = require('node:fs/promises'); - const browsers = require('./.github/workflows/browser_testing.json'); - const excludeFromBstack = Object.values(browsers).map(browser => browser.bsName); + const process = require('process'); + const browsers = Object.entries( + require('./.github/workflows/browser_testing.json') + ).flatMap(([name, browser]) => { + browser = Object.assign({name, version: 'latest'}, browser); + const browsers = [browser]; + const versions = browser.versions; + if (versions) { + delete browser.versions; + browsers.push(...Object.entries(versions).map(([version, def]) => Object.assign({}, browser, {version, ...def}))) + } + return browsers; + }) const bstackBrowsers = Object.fromEntries( - // exlude "latest" version of browsers that we can test on GH actions + // exclude versions of browsers that we can test on GH actions Object.entries(require('./browsers.json')) - .filter(([name, def]) => !excludeFromBstack.includes(def.browser) || def.browser_version !== 'latest') + .filter(([name, def]) => browsers.find(({bsName, version}) => bsName === def.browser && version === def.browser_version) == null) ) const updatedBrowsersJson = JSON.stringify(bstackBrowsers, null, 2); - console.log("Using browsers.json:", updatedBrowsersJson); + let bsBrowsers; + if (process.env.browserstack) { + console.log("Using browsers.json:", updatedBrowsersJson); + bsBrowsers = Object.keys(bstackBrowsers).length; + } else { + console.log("Skipping browserstack tests (credentials are not available)"); + bsBrowsers = 0; + } + console.log("Browsers to be tested directly on runners:", JSON.stringify(browsers, null, 2)) await fs.writeFile('./browsers.json', updatedBrowsersJson); return { - hasBSBrowsers: Object.keys(bstackBrowsers).length > 0, - browsers: Object.entries(browsers).map(([name, def]) => Object.assign({name}, def)) + bsBrowsers, + browsers, + latestBrowsers: browsers.filter(browser => browser.version === 'latest') } - name: "Save working directory" id: bstack-save - if: ${{ fromJSON(steps.define.outputs.result).hasBSBrowsers }} + if: ${{ fromJSON(steps.define.outputs.result).bsBrowsers > 0 }} uses: ./.github/actions/save with: prefix: browserstack- @@ -87,7 +111,7 @@ jobs: unit-tests: needs: [setup, build] - name: "Unit (browser: ${{ matrix.browser.name }})" + name: "Unit (browser: ${{ matrix.browser.name }} ${{ matrix.browser.version }})" strategy: fail-fast: false matrix: @@ -95,6 +119,8 @@ jobs: uses: ./.github/workflows/run-tests.yml with: + install-deb: ${{ matrix.browser.deb }} + install-chrome: ${{ matrix.browser.chrome }} built-key: ${{ needs.build.outputs.built-key }} test-cmd: npx gulp test-only-nobuild --browsers ${{ matrix.browser.name }} ${{ matrix.browser.coverage && '--coverage' || '--no-coverage' }} chunks: 8 @@ -111,6 +137,7 @@ jobs: test-cmd: npx gulp test-only-nobuild --browserstack --no-coverage chunks: 8 browserstack: true + browserstack-sessions: ${{ fromJSON(needs.setup.outputs.bstack-sessions) }} secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} diff --git a/.github/workflows/browser_testing.json b/.github/workflows/browser_testing.json index debfef278e1..46b0894e071 100644 --- a/.github/workflows/browser_testing.json +++ b/.github/workflows/browser_testing.json @@ -2,7 +2,14 @@ "ChromeHeadless": { "bsName": "chrome", "wdioName": "chrome", - "coverage": true + "coverage": true, + "versions": { + "113.0": { + "coverage": false, + "chrome": "113.0.5672.0", + "name": "ChromeNoSandbox" + } + } }, "EdgeHeadless": { "bsName": "edge", diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6942d25b54c..64c3096274a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout if: ${{ inputs.build-cmd }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore source if: ${{ inputs.build-cmd }} uses: ./.github/actions/load diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml index 8d327a7e2b1..f543394f479 100644 --- a/.github/workflows/code-path-changes.yml +++ b/.github/workflows/code-path-changes.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Node.js uses: actions/setup-node@v6 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f86cd38a43c..691ca30b583 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml new file mode 100644 index 00000000000..73fca067c05 --- /dev/null +++ b/.github/workflows/comment.yml @@ -0,0 +1,43 @@ +name: Post a comment +on: + workflow_run: + workflows: + - Check for Duplicated Code + - Check for linter warnings / exceptions + types: + - completed + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + comment: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: master + - name: Retrieve comment data + id: get-comment + uses: ./.github/actions/unzip-artifact + with: + name: comment + + - name: 'Comment on PR' + if: ${{ steps.get-comment.outputs.exists == 'true' }} + uses: actions/github-script@v8 + with: + script: | + const fs = require('fs'); + const path = require('path'); + const temp = '${{ runner.temp }}/artifacts'; + const {issue_number, body} = JSON.parse(fs.readFileSync(path.join(temp, 'comment.json'))); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + body + }); diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 39e54bebcf0..a4b2a14861f 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -1,29 +1,30 @@ name: Check for Duplicated Code on: - pull_request_target: + pull_request: branches: - master +permissions: + contents: read + jobs: check-duplication: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: - fetch-depth: 0 # Fetch all history for all branches - ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: '20' - - name: Install dependencies - run: | - npm install -g jscpd diff-so-fancy + - name: Install jscpd + run: npm install -g jscpd - name: Create jscpd config file run: | @@ -35,26 +36,27 @@ jobs: ], "output": "./", "pattern": "**/*.js", - "ignore": "**/*spec.js" + "ignore": ["**/*spec.js"] }' > .jscpd.json - name: Run jscpd on entire codebase run: jscpd - - name: Fetch base and target branches + - name: Fetch base branch for comparison 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 + git fetch origin refs/heads/${{ github.base_ref }} - - 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: Get changed files + run: | + git diff --name-only FETCH_HEAD...HEAD > changed_files.txt + cat changed_files.txt - name: List generated files (debug) run: ls -l - name: Upload unfiltered jscpd report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: unfiltered-jscpd-report path: ./jscpd-report.json @@ -87,12 +89,12 @@ jobs: - name: Upload filtered jscpd report if: env.filtered_report_exists == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: filtered-jscpd-report path: ./filtered-jscpd-report.json - - name: Post GitHub comment + - name: Generate PR comment if: env.filtered_report_exists == 'true' uses: actions/github-script@v8 with: @@ -101,7 +103,7 @@ jobs: 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}` + return `https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}/${dup.name}#L${dup.start + 1}-L${dup.end - 1}` } filteredReport.forEach(duplication => { const firstFile = duplication.firstFile; @@ -110,12 +112,17 @@ jobs: 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, + fs.writeFileSync('${{ runner.temp }}/comment.json', JSON.stringify({ issue_number: context.issue.number, body: comment - }); + })) + + - name: Upload comment data + if: env.filtered_report_exists == 'true' + uses: actions/upload-artifact@v7 + with: + name: comment + path: ${{ runner.temp }}/comment.json - name: Fail if duplications are found if: env.filtered_report_exists == 'true' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 30d327d3495..e4a736c0b25 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,10 +1,13 @@ name: Check for linter warnings / exceptions on: - pull_request_target: + pull_request: branches: - master +permissions: + contents: read + jobs: check-linter: runs-on: ubuntu-latest @@ -16,7 +19,7 @@ jobs: node-version: '20' - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.event.pull_request.base.sha }} @@ -46,7 +49,9 @@ jobs: - name: Compare them and post comment if necessary uses: actions/github-script@v8 + id: comment with: + result-encoding: string script: | const fs = require('fs'); const path = require('path'); @@ -101,10 +106,18 @@ jobs: const comment = mkComment(mkDiff(base, pr)); if (comment) { - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, + fs.writeFileSync("${{ runner.temp }}/comment.json", JSON.stringify({ issue_number: context.issue.number, body: comment - }); + })) + return "true"; + } else { + return "false"; } + + - name: Upload comment data + if: ${{ steps.comment.outputs.result == 'true' }} + uses: actions/upload-artifact@v7 + with: + name: comment + path: ${{ runner.temp }}/comment.json diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 36b1bca4b4d..924683ec0e0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -28,6 +28,11 @@ on: required: false type: boolean default: false + browserstack-sessions: + description: Number of browserstack sessions needed to run tests + required: false + type: number + default: 6 timeout: description: Timeout on test run required: false @@ -43,11 +48,19 @@ on: type: boolean required: false default: false + install-chrome: + description: Chrome version to install via @puppeteer/browsers + type: string + required: false run-npm-install: description: Run npm install before tests type: boolean required: false default: false + install-deb: + description: URL to deb to install before tests + type: string + required: false outputs: coverage: description: Artifact name for coverage results @@ -58,9 +71,14 @@ on: BROWSERSTACK_ACCESS_KEY: description: "Browserstack access key" + +permissions: + contents: read + actions: read + jobs: checkout: - name: "Set up environment" + name: "Define chunks" runs-on: ubuntu-latest outputs: chunks: ${{ steps.chunks.outputs.chunks }} @@ -87,7 +105,7 @@ jobs: matrix: chunk-no: ${{ fromJSON(needs.checkout.outputs.chunks) }} - name: "Test chunk ${{ matrix.chunk-no }}" + name: Test${{ inputs.chunks > 1 && format(' chunk {0}', matrix.chunk-no) || '' }} env: BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} @@ -107,7 +125,7 @@ jobs: runs-on: ${{ inputs.runs-on }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore source uses: ./.github/actions/load @@ -122,7 +140,21 @@ jobs: defaults write com.apple.Safari AllowRemoteAutomation 1 sudo safaridriver --enable sudo "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" --enable - + + - name: Install Chrome + if: ${{ inputs.install-chrome }} + shell: bash + run: | + out=($(npx @puppeteer/browsers install chrome@${{ inputs.install-chrome }})) + echo 'CHROME_BIN='"${out[1]}" >> env; + cat env + cat env >> "$GITHUB_ENV" + + - name: Install deb + if: ${{ inputs.install-deb }} + uses: ./.github/actions/install-deb + with: + url: ${{ inputs.install-deb }} - name: Run npm install if: ${{ inputs.run-npm-install }} @@ -147,6 +179,8 @@ jobs: - name: 'Wait for browserstack' if: ${{ inputs.browserstack }} uses: ./.github/actions/wait-for-browserstack + with: + sessions: ${{ inputs.browserstack-sessions }} - name: Run tests uses: nick-fields/retry@v3 @@ -172,7 +206,7 @@ jobs: - name: 'Save coverage result' if: ${{ steps.coverage.outputs.coverage }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-partial-${{inputs.test-cmd}}-${{ matrix.chunk-no }} path: ./build/coverage @@ -187,7 +221,7 @@ jobs: coverage: coverage-complete-${{ inputs.test-cmd }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore source uses: ./.github/actions/load @@ -195,7 +229,7 @@ jobs: name: ${{ needs.build.outputs.built-key }} - name: Download coverage results - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: path: ./build/coverage pattern: coverage-partial-${{ inputs.test-cmd }}-* diff --git a/.github/workflows/scripts/assignReviewers.js b/.github/workflows/scripts/assignReviewers.js index 11e62e7e99c..8bbe50f6104 100644 --- a/.github/workflows/scripts/assignReviewers.js +++ b/.github/workflows/scripts/assignReviewers.js @@ -14,15 +14,16 @@ function pickFrom(candidates, exclude, no) { } async function assignReviewers({github, context, prData}) { - const reviewers = prData.review.reviewers.map(rv => rv.login); + const allReviewers = prData.review.reviewers.map(rv => rv.login); + const requestedReviewers = prData.review.requestedReviewers; const missingPrebidEng = prData.review.requires.prebidEngineers - prData.review.prebidEngineers; - const missingPrebidReviewers = prData.review.requires.prebidReviewers - prData.review.prebidReviewers - missingPrebidEng; + const missingPrebidReviewers = prData.review.requires.prebidReviewers - prData.review.prebidReviewers - (missingPrebidEng > 0 ? missingPrebidEng : 0); if (missingPrebidEng > 0) { - reviewers.push(...pickFrom(prData.prebidEngineers, [...reviewers, prData.author.login], missingPrebidEng)) + requestedReviewers.push(...pickFrom(prData.prebidEngineers, [...allReviewers, prData.author.login], missingPrebidEng)) } if (missingPrebidReviewers > 0) { - reviewers.push(...pickFrom(prData.prebidReviewers, [...reviewers, prData.author.login], missingPrebidReviewers)) + requestedReviewers.push(...pickFrom(prData.prebidReviewers, [...allReviewers, prData.author.login], missingPrebidReviewers)) } const request = ghRequester(github); @@ -30,9 +31,9 @@ async function assignReviewers({github, context, prData}) { owner: context.repo.owner, repo: context.repo.repo, prNo: prData.pr, - reviewers + reviewers: requestedReviewers }) - return reviewers; + return requestedReviewers; } module.exports = assignReviewers; diff --git a/.github/workflows/scripts/getPRProperties.js b/.github/workflows/scripts/getPRProperties.js index c4eca6183a1..90273e441a1 100644 --- a/.github/workflows/scripts/getPRProperties.js +++ b/.github/workflows/scripts/getPRProperties.js @@ -1,5 +1,6 @@ const ghRequester = require('./ghRequest.js'); const AWS = require("@aws-sdk/client-s3"); +const fs = require('fs'); const MODULE_PATTERNS = [ /^modules\/([^\/]+)BidAdapter(\.(\w+)|\/)/, @@ -25,7 +26,7 @@ function extractVendor(chunkName) { } const getLibraryRefs = (() => { - const deps = require('../../../build/dist/dependencies.json'); + const deps = JSON.parse(fs.readFileSync(process.env.DEPENDENCIES_JSON).toString()); const refs = {}; return function (libraryName) { if (!refs.hasOwnProperty(libraryName)) { @@ -64,9 +65,9 @@ async function isPrebidMember(ghHandle) { } -async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { +async function getPRProperties({github, context, prNo, reviewerTeam, engTeam, authReviewTeam}) { const request = ghRequester(github); - let [files, pr, prebidReviewers, prebidEngineers] = await Promise.all([ + let [files, pr, prReviews, prebidReviewers, prebidEngineers, authorizedReviewers] = await Promise.all([ request('GET /repos/{owner}/{repo}/pulls/{prNo}/files', { owner: context.repo.owner, repo: context.repo.repo, @@ -77,13 +78,19 @@ async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { repo: context.repo.repo, prNo, }), - ...[reviewerTeam, engTeam].map(team => request('GET /orgs/{org}/teams/{team}/members', { + request('GET /repos/{owner}/{repo}/pulls/{prNo}/reviews', { + owner: context.repo.owner, + repo: context.repo.repo, + prNo, + }), + ...[reviewerTeam, engTeam, authReviewTeam].map(team => request('GET /orgs/{org}/teams/{team}/members', { org: context.repo.owner, team, })) ]); prebidReviewers = prebidReviewers.data.map(datum => datum.login); prebidEngineers = prebidEngineers.data.map(datum=> datum.login); + authorizedReviewers = authorizedReviewers.data.map(datum=> datum.login); let isCoreChange = false; files = files.data.map(datum => datum.filename).map(file => { const core = isCoreFile(file); @@ -96,15 +103,23 @@ async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { const review = { prebidEngineers: 0, prebidReviewers: 0, - reviewers: [] + reviewers: [], + requestedReviewers: [] }; const author = pr.data.user.login; + const allReviewers = new Set(); pr.data.requested_reviewers - .map(rv => rv.login) + .forEach(rv => { + allReviewers.add(rv.login); + review.requestedReviewers.push(rv.login); + }); + prReviews.data.forEach(datum => allReviewers.add(datum.user.login)); + + allReviewers .forEach(reviewer => { if (reviewer === author) return; const isPrebidEngineer = prebidEngineers.includes(reviewer); - const isPrebidReviewer = isPrebidEngineer || prebidReviewers.includes(reviewer); + const isPrebidReviewer = isPrebidEngineer || prebidReviewers.includes(reviewer) || authorizedReviewers.includes(reviewer); if (isPrebidEngineer) { review.prebidEngineers += 1; } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c56b8cae4c8..ed12de000c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,13 @@ on: branches: - master - '*-legacy' - pull_request_target: + pull_request: types: [opened, synchronize, reopened] +permissions: + contents: read + actions: read + concurrency: group: test-${{ github.head_ref || github.ref }} cancel-in-progress: true @@ -32,15 +36,15 @@ jobs: - name: Checkout code (PR) id: checkout-pr - if: ${{ github.event_name == 'pull_request_target' }} - uses: actions/checkout@v5 + if: ${{ github.event_name == 'pull_request' }} + uses: actions/checkout@v6 with: ref: refs/pull/${{ github.event.pull_request.number }}/head - name: Checkout code (push) id: checkout-push if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Commit info id: info @@ -66,7 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore source uses: ./.github/actions/load with: @@ -101,7 +105,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore source uses: ./.github/actions/load diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..4812751a94a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +lockfile-version=2 diff --git a/AGENTS.md b/AGENTS.md index ec1601c61f4..c340fee56ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,7 @@ This file contains instructions for the Codex agent and its friends when working - Always include the string 'codex' or 'agent' in any branch you create. If you instructed to not do that, always include the string 'perbid'. - Do not submit pr's with changes to creative.html or creative.js - Read CONTRIBUTING.md and PR_REVIEW.md for additional context +- Use the guidelines at PR_REVIEW.md when doing PR reviews. Make all your comments and code suggestions on the PR itself instead of in linked tasks when commenting in a PR review. ## Testing - When you modify or add source or test files, run only the affected unit tests. @@ -44,3 +45,6 @@ This file contains instructions for the Codex agent and its friends when working - Avoid running Babel over the entire project for incremental test runs. - Use `gulp serve-and-test --file ` or `gulp test --file` so Babel processes only the specified files. - Do not invoke commands that rebuild all modules when only a subset are changed. + +## Additional context +- for additional context on repo history, consult https://github.com/prebid/github-activity-db/blob/main/CLAUDE.md on how to download and access repo history in a database you can search locally. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 606d26cd25a..b7a797beeb4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ Prebid uses [Mocha](http://mochajs.org/) and [Chai](http://chaijs.com/) for unit provides mocks, stubs, and spies. [Karma](https://karma-runner.github.io/1.0/index.html) runs the tests and generates code coverage reports at `build/coverage/lcov/lcov-report/index.html`. -Tests are stored in the [test/spec](test/spec) directory. Tests for Adapters are located in [test/spec/adapters](test/spec/adapters). +Tests are stored in the [test/spec](test/spec) directory. Tests for Adapters are located in [test/spec/modules](test/spec/modules). They can be run with the following commands: - `gulp test` - run the test suite once (`npm test` is aliased to call `gulp test`) diff --git a/browsers.json b/browsers.json index 974df030ee7..f37d708221e 100644 --- a/browsers.json +++ b/browsers.json @@ -15,11 +15,11 @@ "device": null, "os": "Windows" }, - "bs_chrome_109_windows_10": { + "bs_chrome_113_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "109.0", + "browser_version": "113.0", "device": null, "os": "Windows" }, @@ -47,5 +47,4 @@ "device": null, "os": "OS X" } - } diff --git a/eslint.config.js b/eslint.config.js index a9b7fe04153..46d0eb86da4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,6 +9,7 @@ const path = require('path'); const _ = require('lodash'); const tseslint = require('typescript-eslint'); const {getSourceFolders} = require('./gulpHelpers.js'); +const APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS = require('./plugins/eslint/approvedLoadExternalScriptPaths.js'); function jsPattern(name) { return [`${name}/**/*.js`, `${name}/**/*.mjs`] @@ -122,6 +123,13 @@ module.exports = [ message: "Assigning a function to 'logResult, 'logMessage', 'logInfo', 'logWarn', or 'logError' is not allowed." }, ], + 'no-restricted-imports': [ + 'error', { + patterns: [ + '**/src/adloader.js' + ] + } + ], // Exceptions below this line are temporary (TM), so that eslint can be added into the CI process. // Violations of these styles should be fixed, and the exceptions removed over time. @@ -273,4 +281,22 @@ module.exports = [ '@typescript-eslint/no-require-imports': 'off' } }, -] + // Override: allow loadExternalScript import in approved files (excluding BidAdapters) + { + files: APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS.filter(p => !p.includes('BidAdapter')).map(p => { + // If path doesn't end with .js/.ts/.mjs, treat as folder pattern + if (!p.match(/\.(js|ts|mjs)$/)) { + return `${p}/**/*.{js,ts,mjs}`; + } + return p; + }), + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [] + } + ], + } + }, + ] diff --git a/gulpfile.js b/gulpfile.js index e5a4db41884..cc543ad73ec 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -53,7 +53,7 @@ function bundleToStdout() { bundleToStdout.displayName = 'bundle-to-stdout'; function clean() { - return gulp.src(['build', 'dist'], { + return gulp.src(['.cache', 'build', 'dist'], { read: false, allowEmpty: true }) diff --git a/integrationExamples/audio/audioGam.html b/integrationExamples/audio/audioGam.html index 8a8a4398637..6392e5f0b6e 100644 --- a/integrationExamples/audio/audioGam.html +++ b/integrationExamples/audio/audioGam.html @@ -115,9 +115,11 @@ const bid = bidResponse.bids[0]; + const adUnit = adUnits.find(au => au.code === 'div-gpt-ad-51545-0'); + const adXml = await pbjs.adServers.gam.getVastXml({ bid, - adUnit: 'div-gpt-ad-51545-0', + adUnit, params: { iu: '/41758329/localcache', url: "https://pubads.g.doubleclick.net/gampad/ads?iu=/41758329/localcache&sz=640x480&gdfp_req=1&output=vast&env=vp", diff --git a/integrationExamples/chromeai/japanese.html b/integrationExamples/chromeai/japanese.html index fd212b03550..3d7c5954090 100644 --- a/integrationExamples/chromeai/japanese.html +++ b/integrationExamples/chromeai/japanese.html @@ -135,8 +135,8 @@ enabled: true }, summarizer:{ - enabled: false, - length: 190 + enabled: true, + length: "medium" } }, }, diff --git a/integrationExamples/gpt/adcluster_banner_example.html b/integrationExamples/gpt/adcluster_banner_example.html new file mode 100644 index 00000000000..4f7bf646bb9 --- /dev/null +++ b/integrationExamples/gpt/adcluster_banner_example.html @@ -0,0 +1,115 @@ + + + + + Adcluster Adapter Test + + + + + + + + + + +

Prebid.js Live Adapter Test

+
+ + + + diff --git a/integrationExamples/gpt/adcluster_video_example.html b/integrationExamples/gpt/adcluster_video_example.html new file mode 100644 index 00000000000..0a309b24749 --- /dev/null +++ b/integrationExamples/gpt/adcluster_video_example.html @@ -0,0 +1,291 @@ + + + + + Adcluster Adapter – Outstream Test with Fallback + + + + + + + + + +

Adcluster Adapter – Outstream Test (AN renderer + IMA fallback)

+
+ +
+ + + + diff --git a/integrationExamples/gpt/insurads.html b/integrationExamples/gpt/insurads.html new file mode 100644 index 00000000000..92f6b7df8b2 --- /dev/null +++ b/integrationExamples/gpt/insurads.html @@ -0,0 +1,121 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/integrationExamples/gpt/localCacheGam.html b/integrationExamples/gpt/localCacheGam.html index 6b203d33ee9..9169fcdf5e3 100644 --- a/integrationExamples/gpt/localCacheGam.html +++ b/integrationExamples/gpt/localCacheGam.html @@ -13,6 +13,8 @@ mediaTypes: { video: { playerSize: [640, 360], + playbackmethod: [2, 6], + api: [2, 7, 8], } }, video: { @@ -95,9 +97,11 @@ const bid = bidResponse.bids[0]; + const adUnit = adUnits.find(au => au.code === 'div-gpt-ad-51545-0'); + const vastXml = await pbjs.adServers.gam.getVastXml({ bid, - adUnit: 'div-gpt-ad-51545-0', + adUnit, params: { iu: '/41758329/localcache', url: "https://pubads.g.doubleclick.net/gampad/ads?iu=/41758329/localcache&sz=640x480&gdfp_req=1&output=vast&env=vp", diff --git a/integrationExamples/gpt/mediago_test.html b/integrationExamples/gpt/mediago_test.html new file mode 100644 index 00000000000..d09e814eda4 --- /dev/null +++ b/integrationExamples/gpt/mediago_test.html @@ -0,0 +1,442 @@ + + + + + + Mediago Bid Adapter Test + + + + + + +

Mediago Bid Adapter Test Page

+

This page is used to verify that the ID uniqueness issue has been resolved when there are multiple ad units

+ +
+ + +
+ +
+

Waiting for request...

+

Click the "Run Auction" button to start testing

+
+ +
+

Waiting for response...

+
+ +
+

Ad Unit 1 (300x250) - mpu_left

+
+

Ad Unit 1 - mpu_left

+
+
+ +
+

Ad Unit 2 (300x250)

+
+

Ad Unit 2

+
+
+ +
+

Ad Unit 3 (728x90)

+
+

Ad Unit 3

+
+
+ + + + + + diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index 3d6fef98995..68c95fe8b4f 100644 --- a/integrationExamples/gpt/neuwoRtdProvider_example.html +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -26,28 +26,28 @@ var adUnits = [ { - code: '/19968336/header-bid-tag-1', + code: "/19968336/header-bid-tag-1", mediaTypes: { banner: { sizes: div_1_sizes } }, bids: [{ - bidder: 'appnexus', + bidder: "appnexus", params: { placementId: 13144370 } }] }, { - code: '/19968336/header-bid-tag-1', + code: "/19968336/header-bid-tag-1", mediaTypes: { banner: { sizes: div_2_sizes } }, bids: [{ - bidder: 'appnexus', + bidder: "appnexus", params: { placementId: 13144370 } @@ -86,12 +86,12 @@ // Custom Timeout logic in onSettingsUpdate() googletag.cmd.push(function () { - googletag.defineSlot('/19968336/header-bid-tag-1', div_1_sizes, 'div-1').addService(googletag.pubads()); + googletag.defineSlot("/19968336/header-bid-tag-1", div_1_sizes, "div-1").addService(googletag.pubads()); googletag.pubads().enableSingleRequest(); googletag.enableServices(); }); googletag.cmd.push(function () { - googletag.defineSlot('/19968336/header-bid-tag-1', div_2_sizes, 'div-2').addService(googletag.pubads()); + googletag.defineSlot("/19968336/header-bid-tag-1", div_2_sizes, "div-2").addService(googletag.pubads()); googletag.pubads().enableSingleRequest(); googletag.enableServices(); }); @@ -99,47 +99,65 @@ // 3. User Triggered Setup (RTD Module) function onSettingsUpdate() { - const inputNeuwoApiToken = document.getElementById('neuwo-api-token'); - const neuwoApiToken = inputNeuwoApiToken ? inputNeuwoApiToken.value : ''; + const inputNeuwoApiToken = document.getElementById("neuwo-api-token"); + const neuwoApiToken = inputNeuwoApiToken ? inputNeuwoApiToken.value : ""; if (!neuwoApiToken) { - alert('Please enter your token for Neuwo AI API to the field'); + alert("Please enter your token for Neuwo AI API to the field"); if (inputNeuwoApiToken) inputNeuwoApiToken.focus(); return; } - const inputNeuwoApiUrl = document.getElementById('neuwo-api-url'); - const neuwoApiUrl = inputNeuwoApiUrl ? inputNeuwoApiUrl.value : ''; + const inputNeuwoApiUrl = document.getElementById("neuwo-api-url"); + const neuwoApiUrl = inputNeuwoApiUrl ? inputNeuwoApiUrl.value : ""; if (!neuwoApiUrl) { - alert('Please enter Neuwo AI API url to the field'); + alert("Please enter Neuwo AI API url to the field"); if (inputNeuwoApiUrl) inputNeuwoApiUrl.focus(); return; } - const inputWebsiteToAnalyseUrl = document.getElementById('website-to-analyse-url'); + const inputWebsiteToAnalyseUrl = document.getElementById("website-to-analyse-url"); const websiteToAnalyseUrl = inputWebsiteToAnalyseUrl ? inputWebsiteToAnalyseUrl.value : undefined; - const inputIabContentTaxonomyVersion = document.getElementById('iab-content-taxonomy-version'); + const inputIabContentTaxonomyVersion = document.getElementById("iab-content-taxonomy-version"); const iabContentTaxonomyVersion = inputIabContentTaxonomyVersion ? inputIabContentTaxonomyVersion.value : undefined; // Cache Option - const inputEnableCache = document.getElementById('enable-cache'); + const inputEnableCache = document.getElementById("enable-cache"); const enableCache = inputEnableCache ? inputEnableCache.checked : undefined; + // OpenRTB 2.5 Category Fields Option + const inputEnableOrtb25Fields = document.getElementById("enable-ortb25-fields"); + const enableOrtb25Fields = inputEnableOrtb25Fields ? inputEnableOrtb25Fields.checked : true; + // URL Stripping Options - const inputStripAllQueryParams = document.getElementById('strip-all-query-params'); + const inputStripAllQueryParams = document.getElementById("strip-all-query-params"); const stripAllQueryParams = inputStripAllQueryParams ? inputStripAllQueryParams.checked : undefined; - const inputStripQueryParamsForDomains = document.getElementById('strip-query-params-for-domains'); - const stripQueryParamsForDomainsValue = inputStripQueryParamsForDomains ? inputStripQueryParamsForDomains.value.trim() : ''; - const stripQueryParamsForDomains = stripQueryParamsForDomainsValue ? stripQueryParamsForDomainsValue.split(',').map(d => d.trim()).filter(d => d) : undefined; + const inputStripQueryParamsForDomains = document.getElementById("strip-query-params-for-domains"); + const stripQueryParamsForDomainsValue = inputStripQueryParamsForDomains ? inputStripQueryParamsForDomains.value.trim() : ""; + const stripQueryParamsForDomains = stripQueryParamsForDomainsValue ? stripQueryParamsForDomainsValue.split(",").map(d => d.trim()).filter(d => d) : undefined; - const inputStripQueryParams = document.getElementById('strip-query-params'); - const stripQueryParamsValue = inputStripQueryParams ? inputStripQueryParams.value.trim() : ''; - const stripQueryParams = stripQueryParamsValue ? stripQueryParamsValue.split(',').map(p => p.trim()).filter(p => p) : undefined; + const inputStripQueryParams = document.getElementById("strip-query-params"); + const stripQueryParamsValue = inputStripQueryParams ? inputStripQueryParams.value.trim() : ""; + const stripQueryParams = stripQueryParamsValue ? stripQueryParamsValue.split(",").map(p => p.trim()).filter(p => p) : undefined; - const inputStripFragments = document.getElementById('strip-fragments'); + const inputStripFragments = document.getElementById("strip-fragments"); const stripFragments = inputStripFragments ? inputStripFragments.checked : undefined; + // IAB Taxonomy Filtering Option + const inputEnableFiltering = document.getElementById("enable-iab-filtering"); + const enableIabFiltering = inputEnableFiltering ? inputEnableFiltering.checked : false; + + // Build iabTaxonomyFilters object only if filtering is enabled + const iabTaxonomyFilters = enableIabFiltering ? { + ContentTier1: { limit: 1, threshold: 0.1 }, + ContentTier2: { limit: 2, threshold: 0.1 }, + ContentTier3: { limit: 3, threshold: 0.15 }, + AudienceTier3: { limit: 3, threshold: 0.2 }, + AudienceTier4: { limit: 5, threshold: 0.2 }, + AudienceTier5: { limit: 7, threshold: 0.3 }, + } : undefined; + pbjs.que.push(function () { pbjs.setConfig({ debug: true, @@ -155,10 +173,12 @@ websiteToAnalyseUrl, iabContentTaxonomyVersion, enableCache, + enableOrtb25Fields, stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, - stripFragments + stripFragments, + iabTaxonomyFilters, } } ] @@ -185,7 +205,7 @@

Basic Prebid.js Example using Neuwo Rtd Provider

- Looks like you're not following the testing environment setup, try accessing + Looks like you"re not following the testing environment setup, try accessing http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -215,12 +235,14 @@

Neuwo Rtd Provider Configuration

- +

IAB Content Taxonomy Options

- +

Cache Options

@@ -231,6 +253,14 @@

Cache Options

+

OpenRTB 2.5 Category Fields

+
+ +
+

URL Cleaning Options

- +
- +
+

IAB Taxonomy Filtering Options

+
+ +
+ When enabled, uses these hardcoded filters:
+ • ContentTier1: top 1 (≥10% relevance)
+ • ContentTier2: top 2 (≥10% relevance)
+ • ContentTier3: top 3 (≥15% relevance)
+ • AudienceTier3: top 3 (≥20% relevance)
+ • AudienceTier4: top 5 (≥20% relevance)
+ • AudienceTier5: top 7 (≥30% relevance) +
+
+ @@ -259,10 +309,10 @@

Ad Examples

Div-1

-
- Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click @@ -272,10 +322,10 @@

Div-1

Div-2

-
- Ad spot div-2: This content will be replaced by prebid.js and/or related components once you click @@ -286,82 +336,327 @@

Div-2

Neuwo Data in Bid Request

-

The retrieved data from Neuwo API is injected into the bid request as OpenRTB (ORTB2)`site.content.data` and - `user.data`. Full bid request can be inspected in Developer Tools Console under +

The retrieved data from Neuwo API is injected into the bid request as OpenRTB (ORTB2) + site.content.data and + user.data. Full bid request can be inspected in Developer Tools Console under INFO: NeuwoRTDModule injectIabCategories: post-injection bidsConfig

+

Neuwo Site Content Data

+
No data yet. Click "Update" to fetch data.
+

Neuwo User Data

+
No data yet. Click "Update" to fetch data.
+

Neuwo OpenRTB 2.5 Category Fields (IAB Content Taxonomy 1.0) Data

+
No data yet. Click "Update" to fetch data (requires enableOrtb25Fields and /v1/iab endpoint).
+
+ +
+

Accessing Neuwo Data in JavaScript

+

Listen to the bidRequested event to access the enriched ORTB2 data:

+
+pbjs.onEvent("bidRequested", function(bidRequest) {
+    const ortb2 = bidRequest.ortb2;
+    const neuwoSiteData = ortb2?.site?.content?.data?.find(d => d.name === "www.neuwo.ai");
+    const neuwoUserData = ortb2?.user?.data?.find(d => d.name === "www.neuwo.ai");
+    console.log("Neuwo data:", { siteContent: neuwoSiteData, user: neuwoUserData });
+});
+        
+

After clicking "Update", the Neuwo data is stored in the global neuwoData variable. Open + Developer Tools Console to see the logged data.

+

Note: Event timing tests for multiple Prebid.js events (auctionInit, bidRequested, + beforeBidderHttp, bidResponse, auctionEnd) are available in the page source code but are commented out. To + enable them, uncomment the timing test section in the JavaScript code.

+
+ +
+

For more information about Neuwo RTD Module configuration and accessing data retrieved from Neuwo API, see modules/neuwoRtdProvider.md.

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

Prebid.js Test

+
Div-1111
+
+ +
+
+ + +
Div 2
+
+ +
+ + diff --git a/integrationExamples/shapingRules/rules.json b/integrationExamples/shapingRules/rules.json new file mode 100644 index 00000000000..3be6ecfb574 --- /dev/null +++ b/integrationExamples/shapingRules/rules.json @@ -0,0 +1,151 @@ +{ + "enabled": true, + "generateRulesFromBidderConfig": true, + "timestamp": "20250131 00:00:00", + "ruleSets": [ + { + "stage": "processed-auction-request", + "name": "exclude-in-jpn", + "version": "1234", + "modelGroups": [ + { + "weight": 98, + "analyticsKey": "experiment-name", + "version": "4567", + "schema": [ + { "function": "deviceCountryIn", "args": [["JPN"]] } + ], + "default": [], + "rules": [ + { + "conditions": ["true"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { "bidders": ["testBidder"], "seatnonbid": 203, "analyticsValue": "rmjpn" }, + { "bidders": ["bidderD"], "seatnonbid": 203, "ifSyncedId": false, "analyticsValue": "rmjpn" } + ] + } + ] + }, + { + "conditions": [], + "results": [] + } + ] + }, + { + "weight": 2, + "analyticsKey": "experiment-name-2", + "version": "4567", + "schema": [ + { "function": "adUnitCode" } + ], + "default": [], + "rules": [ + { + "conditions": ["adUnit-0000"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { "bidders": ["testBidder"], "seatnonbid": 203, "analyticsValue": "rmjpn" }, + { "bidders": ["bidderD"], "seatnonbid": 203, "ifSyncedId": false, "analyticsValue": "rmjpn" } + ] + } + ] + } + ] + } + ] + }, + { + "stage": "processed-auction", + "modelGroups": [ + { + "schema": [{ "function": "percent", "args": [5] }], + "analyticsKey": "bidderC-testing", + "default": [ + { + "function": "logAtag", + "args": { "analyticsValue": "default-allow" } + } + ], + "rules": [ + { + "conditions": ["false"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { + "bidders": ["bidderC"], + "seatnonbid": 203, + "analyticsValue": "excluded" + } + ] + } + ] + } + ] + } + ] + }, + { + "stage": "processed-auction", + "modelGroups": [ + { + "schema": [ + { + "function": "deviceCountry", "args": ["USA"] + } + ], + "rules": [ + { + "conditions": ["true"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { + "bidders": ["bidderM", "bidderN", "bidderO", "bidderP"], + "seatNonBid": 203 + } + ] + } + ] + } + ] + } + ] + }, + { + "stage": "processed-auction", + "modelGroups": [ + { + "schema": [ + { "function": "deviceCountryIn", "args": [["USA", "CAN"]] } + ], + "analyticsKey": "bidder-yaml", + "rules": [ + { + "conditions": ["false"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { + "bidders": ["bidderX"], + "seatNonBid": 203 + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/integrationExamples/shapingRules/shapingRulesModule.html b/integrationExamples/shapingRules/shapingRulesModule.html new file mode 100644 index 00000000000..8843348e731 --- /dev/null +++ b/integrationExamples/shapingRules/shapingRulesModule.html @@ -0,0 +1,168 @@ + + + Prebid Test Bidder Example + + + + +

Prebid Test Bidder Example

+
Banner ad
+ + + diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 6866b296c3e..f6f9d903d58 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -85,13 +85,19 @@ function setReporters(karmaConf, codeCoverage, browserstack, chunkNo) { } function setBrowsers(karmaConf, browserstack) { + karmaConf.customLaunchers = karmaConf.customLaunchers || {}; + karmaConf.customLaunchers.ChromeNoSandbox = { + base: 'ChromeHeadless', + // disable sandbox - necessary within Docker and when using versions installed through @puppeteer/browsers + flags: ['--no-sandbox'] + } if (browserstack) { karmaConf.browserStack = { username: process.env.BROWSERSTACK_USERNAME, accessKey: process.env.BROWSERSTACK_ACCESS_KEY, build: process.env.BROWSERSTACK_BUILD_NAME } - if (process.env.TRAVIS) { + if (process.env.BROWSERSTACK_LOCAL_IDENTIFIER) { karmaConf.browserStack.startTunnel = false; karmaConf.browserStack.tunnelIdentifier = process.env.BROWSERSTACK_LOCAL_IDENTIFIER; } @@ -100,14 +106,7 @@ function setBrowsers(karmaConf, browserstack) { } else { var isDocker = require('is-docker')(); if (isDocker) { - karmaConf.customLaunchers = karmaConf.customLaunchers || {}; - karmaConf.customLaunchers.ChromeCustom = { - base: 'ChromeHeadless', - // We must disable the Chrome sandbox when running Chrome inside Docker (Chrome's sandbox needs - // more permissions than Docker allows by default) - flags: ['--no-sandbox'] - } - karmaConf.browsers = ['ChromeCustom']; + karmaConf.browsers = ['ChromeNoSandbox']; } else { karmaConf.browsers = ['ChromeHeadless']; } diff --git a/libraries/appnexusUtils/anUtils.js b/libraries/appnexusUtils/anUtils.js index 89cbaa95040..6a87625162b 100644 --- a/libraries/appnexusUtils/anUtils.js +++ b/libraries/appnexusUtils/anUtils.js @@ -12,7 +12,6 @@ export function convertCamelToUnderscore(value) { export const appnexusAliases = [ { code: 'appnexusAst', gvlid: 32 }, - { code: 'emetriq', gvlid: 213 }, { code: 'pagescience', gvlid: 32 }, { code: 'gourmetads', gvlid: 32 }, { code: 'newdream', gvlid: 32 }, diff --git a/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js b/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js new file mode 100644 index 00000000000..721df815159 --- /dev/null +++ b/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js @@ -0,0 +1,119 @@ +import { logInfo } from '../../src/utils.js'; + +// this allows the stubbing of functions during testing +export const bidderTimeoutFunctions = { + getDeviceType, + checkVideo, + getConnectionSpeed, + calculateTimeoutModifier +}; + +/** + * Returns an array of a given object's own enumerable string-keyed property [key, value] pairs. + * @param {Object} obj + * @return {Array} + */ +const entries = Object.entries || function (obj) { + const ownProps = Object.keys(obj); + let i = ownProps.length; + let resArray = new Array(i); + while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; } + return resArray; +}; + +function getDeviceType() { + const userAgent = window.navigator.userAgent.toLowerCase(); + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(userAgent))) { + return 5; // tablet + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(userAgent))) { + return 4; // mobile + } + return 2; // personal computer +} + +function checkVideo(adUnits) { + return adUnits.some((adUnit) => { + return adUnit.mediaTypes && adUnit.mediaTypes.video; + }); +} + +function getConnectionSpeed() { + const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection || {} + const connectionType = connection.type || connection.effectiveType; + + switch (connectionType) { + case 'slow-2g': + case '2g': + return 'slow'; + + case '3g': + return 'medium'; + + case 'bluetooth': + case 'cellular': + case 'ethernet': + case 'wifi': + case 'wimax': + case '4g': + return 'fast'; + } + + return 'unknown'; +} + +/** + * Calculate the time to be added to the timeout + * @param {Array} adUnits + * @param {Object} rules + * @return {number} + */ +function calculateTimeoutModifier(adUnits, rules) { + if (!rules) { + return 0; + } + + logInfo('Timeout rules', rules); + let timeoutModifier = 0; + let toAdd = 0; + + if (rules.includesVideo) { + const hasVideo = bidderTimeoutFunctions.checkVideo(adUnits); + toAdd = rules.includesVideo[hasVideo] || 0; + logInfo(`Adding ${toAdd} to timeout for includesVideo ${hasVideo}`) + timeoutModifier += toAdd; + } + + if (rules.numAdUnits) { + const numAdUnits = adUnits.length; + if (rules.numAdUnits[numAdUnits]) { + timeoutModifier += rules.numAdUnits[numAdUnits]; + } else { + for (const [rangeStr, timeoutVal] of entries(rules.numAdUnits)) { + const [lowerBound, upperBound] = rangeStr.split('-'); + if (parseInt(lowerBound) <= numAdUnits && numAdUnits <= parseInt(upperBound)) { + logInfo(`Adding ${timeoutVal} to timeout for numAdUnits ${numAdUnits}`) + timeoutModifier += timeoutVal; + break; + } + } + } + } + + if (rules.deviceType) { + const deviceType = bidderTimeoutFunctions.getDeviceType(); + toAdd = rules.deviceType[deviceType] || 0; + logInfo(`Adding ${toAdd} to timeout for deviceType ${deviceType}`) + timeoutModifier += toAdd; + } + + if (rules.connectionSpeed) { + const connectionSpeed = bidderTimeoutFunctions.getConnectionSpeed(); + toAdd = rules.connectionSpeed[connectionSpeed] || 0; + logInfo(`Adding ${toAdd} to timeout for connectionSpeed ${connectionSpeed}`) + timeoutModifier += toAdd; + } + + logInfo('timeout Modifier calculated', timeoutModifier); + return timeoutModifier; +} diff --git a/libraries/connectionInfo/connectionUtils.js b/libraries/connectionInfo/connectionUtils.js index 29d1e310986..562872d75ba 100644 --- a/libraries/connectionInfo/connectionUtils.js +++ b/libraries/connectionInfo/connectionUtils.js @@ -3,11 +3,51 @@ * * @returns {number} - Type of connection. */ +function resolveNavigator() { + if (typeof window !== 'undefined' && window.navigator) { + return window.navigator; + } + + if (typeof navigator !== 'undefined') { + return navigator; + } + + return null; +} + +function resolveNetworkInformation() { + const nav = resolveNavigator(); + if (!nav) { + return null; + } + + return nav.connection || nav.mozConnection || nav.webkitConnection || null; +} + +export function getConnectionInfo() { + const connection = resolveNetworkInformation(); + + if (!connection) { + return null; + } + + return { + type: connection.type ?? null, + effectiveType: connection.effectiveType ?? null, + downlink: typeof connection.downlink === 'number' ? connection.downlink : null, + downlinkMax: typeof connection.downlinkMax === 'number' ? connection.downlinkMax : null, + rtt: typeof connection.rtt === 'number' ? connection.rtt : null, + saveData: typeof connection.saveData === 'boolean' ? connection.saveData : null, + bandwidth: typeof connection.bandwidth === 'number' ? connection.bandwidth : null + }; +} + export function getConnectionType() { - const connection = navigator.connection || navigator.webkitConnection; + const connection = getConnectionInfo(); if (!connection) { return 0; } + switch (connection.type) { case 'ethernet': return 1; diff --git a/libraries/devicePixelRatio/devicePixelRatio.js b/libraries/devicePixelRatio/devicePixelRatio.js new file mode 100644 index 00000000000..c206ed053b3 --- /dev/null +++ b/libraries/devicePixelRatio/devicePixelRatio.js @@ -0,0 +1,13 @@ +import {isFingerprintingApiDisabled} from '../fingerprinting/fingerprinting.js'; +import {getFallbackWindow} from '../../src/utils.js'; + +export function getDevicePixelRatio(win) { + if (isFingerprintingApiDisabled('devicepixelratio')) { + return 1; + } + try { + return getFallbackWindow(win).devicePixelRatio; + } catch (e) { + } + return 1; +} diff --git a/libraries/fingerprinting/fingerprinting.js b/libraries/fingerprinting/fingerprinting.js new file mode 100644 index 00000000000..aba529d0c8f --- /dev/null +++ b/libraries/fingerprinting/fingerprinting.js @@ -0,0 +1,12 @@ +import {config} from '../../src/config.js'; + +/** + * Returns true if the given fingerprinting API is disabled via setConfig({ disableFingerprintingApis: [...] }). + * Comparison is case-insensitive. Use for 'devicepixelratio', 'webdriver', 'resolvedoptions', 'screen'. + * @param {string} apiName + * @returns {boolean} + */ +export function isFingerprintingApiDisabled(apiName) { + const list = config.getConfig('disableFingerprintingApis'); + return Array.isArray(list) && list.some((item) => String(item).toLowerCase() === apiName.toLowerCase()); +} diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index 0992f4c26b3..6bcf994d2b4 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -1,19 +1,20 @@ -export const FIRST_PARTY_KEY = '_iiq_fdata'; +export const FIRST_PARTY_KEY = "_iiq_fdata"; -export const SUPPORTED_TYPES = ['html5', 'cookie'] +export const SUPPORTED_TYPES = ["html5", "cookie"]; -export const WITH_IIQ = 'A'; -export const WITHOUT_IIQ = 'B'; -export const NOT_YET_DEFINED = 'U'; -export const BLACK_LIST = 'L'; -export const CLIENT_HINTS_KEY = '_iiq_ch'; -export const EMPTY = 'EMPTY'; -export const GVLID = '1323'; -export const VERSION = 0.32; -export const PREBID = 'pbjs'; +export const WITH_IIQ = "A"; +export const WITHOUT_IIQ = "B"; +export const DEFAULT_PERCENTAGE = 95; +export const BLACK_LIST = "L"; +export const CLIENT_HINTS_KEY = "_iiq_ch"; +export const EMPTY = "EMPTY"; +export const GVLID = "1323"; +export const VERSION = 0.33; +export const PREBID = "pbjs"; export const HOURS_24 = 86400000; +export const HOURS_72 = HOURS_24 * 3; -export const INVALID_ID = 'INVALID_ID'; +export const INVALID_ID = "INVALID_ID"; export const SYNC_REFRESH_MILL = 3600000; export const META_DATA_CONSTANT = 256; @@ -25,10 +26,24 @@ export const MAX_REQUEST_LENGTH = { opera: 2097152, edge: 2048, firefox: 65536, - ie: 2048 + ie: 2048, }; export const CH_KEYS = [ - 'brands', 'mobile', 'platform', 'bitness', 'wow64', 'architecture', - 'model', 'platformVersion', 'fullVersionList' + "brands", + "mobile", + "platform", + "bitness", + "wow64", + "architecture", + "model", + "platformVersion", + "fullVersionList", ]; + +export const AB_CONFIG_SOURCE = { + PERCENTAGE: "percentage", + GROUP: "group", + IIQ_SERVER: "IIQServer", + DISABLED: "disabled", +}; diff --git a/libraries/intentIqUtils/defineABTestingGroupUtils.js b/libraries/intentIqUtils/defineABTestingGroupUtils.js new file mode 100644 index 00000000000..af810ac278c --- /dev/null +++ b/libraries/intentIqUtils/defineABTestingGroupUtils.js @@ -0,0 +1,74 @@ +import { + WITH_IIQ, + WITHOUT_IIQ, + DEFAULT_PERCENTAGE, + AB_CONFIG_SOURCE, +} from "../intentIqConstants/intentIqConstants.js"; + +/** + * Fix percentage if provided some incorrect data + * clampPct(150) => 100 + * clampPct(-5) => 0 + * clampPct('abc') => DEFAULT_PERCENTAGE + */ +function clampPct(val) { + const n = Number(val); + if (!Number.isFinite(n)) return DEFAULT_PERCENTAGE; // fallback = 95 + return Math.max(0, Math.min(100, n)); +} + +/** + * Randomly assigns a user to group A or B based on the given percentage. + * Generates a random number (1–100) and compares it with the percentage. + * + * @param {number} pct The percentage threshold (0–100). + * @returns {string} Returns WITH_IIQ for Group A or WITHOUT_IIQ for Group B. + */ +function pickABByPercentage(pct) { + const percentageToUse = + typeof pct === "number" ? pct : DEFAULT_PERCENTAGE; + const percentage = clampPct(percentageToUse); + const roll = Math.floor(Math.random() * 100) + 1; + return roll <= percentage ? WITH_IIQ : WITHOUT_IIQ; // A : B +} + +function configurationSourceGroupInitialization(group) { + return typeof group === 'string' && group.toUpperCase() === WITHOUT_IIQ ? WITHOUT_IIQ : WITH_IIQ; +} + +/** + * Determines the runtime A/B testing group without saving it to Local Storage. + * 1. If terminationCause (tc) exists: + * - tc = 41 → Group B (WITHOUT_IIQ) + * - any other value → Group A (WITH_IIQ) + * 2. Otherwise, assigns the group randomly based on DEFAULT_PERCENTAGE (default 95% for A, 5% for B). + * + * @param {number} [tc] The termination cause value returned by the server. + * @param {number} [abPercentage] A/B percentage provided by partner. + * @returns {string} The determined group: WITH_IIQ (A) or WITHOUT_IIQ (B). + */ + +function IIQServerConfigurationSource(tc, abPercentage) { + if (typeof tc === "number" && Number.isFinite(tc)) { + return tc === 41 ? WITHOUT_IIQ : WITH_IIQ; + } + + return pickABByPercentage(abPercentage); +} + +export function defineABTestingGroup(configObject, tc) { + switch (configObject.ABTestingConfigurationSource) { + case AB_CONFIG_SOURCE.GROUP: + return configurationSourceGroupInitialization( + configObject.group + ); + case AB_CONFIG_SOURCE.PERCENTAGE: + return pickABByPercentage(configObject.abPercentage); + default: { + if (!configObject.ABTestingConfigurationSource) { + configObject.ABTestingConfigurationSource = AB_CONFIG_SOURCE.IIQ_SERVER; + } + return IIQServerConfigurationSource(tc, configObject.abPercentage); + } + } +} diff --git a/libraries/intentIqUtils/gamPredictionReport.js b/libraries/intentIqUtils/gamPredictionReport.js index 09db065f494..2191ade6d35 100644 --- a/libraries/intentIqUtils/gamPredictionReport.js +++ b/libraries/intentIqUtils/gamPredictionReport.js @@ -55,6 +55,7 @@ export function gamPredictionReport (gamObjectReference, sendData) { dataToSend.originalCurrency = bid.originalCurrency; dataToSend.status = bid.status; dataToSend.prebidAuctionId = element.args?.auctionId; + if (!dataToSend.bidderCode) dataToSend.bidderCode = 'GAM'; }; if (dataToSend.bidderCode) { const relevantBid = element.args?.bidsReceived.find( @@ -86,12 +87,12 @@ export function gamPredictionReport (gamObjectReference, sendData) { gamObjectReference.pubads().addEventListener('slotRenderEnded', (event) => { if (event.isEmpty) return; const data = extractWinData(event); - if (data?.cpm) { + if (data) { sendData(data); } }); }); } catch (error) { - this.logger.error('Failed to subscribe to GAM: ' + error); + logError('Failed to subscribe to GAM: ' + error); } }; diff --git a/libraries/intentIqUtils/getRefferer.js b/libraries/intentIqUtils/getRefferer.js index 20c6a6a5b47..2ed8a668a56 100644 --- a/libraries/intentIqUtils/getRefferer.js +++ b/libraries/intentIqUtils/getRefferer.js @@ -4,19 +4,25 @@ import { getWindowTop, logError, getWindowLocation, getWindowSelf } from '../../ * Determines if the script is running inside an iframe and retrieves the URL. * @return {string} The encoded vrref value representing the relevant URL. */ -export function getReferrer() { + +export function getCurrentUrl() { + let url = ''; try { - const url = getWindowSelf() === getWindowTop() - ? getWindowLocation().href - : getWindowTop().location.href; + if (getWindowSelf() === getWindowTop()) { + // top page + url = getWindowLocation().href || ''; + } else { + // iframe + url = getWindowTop().location.href || ''; + } if (url.length >= 50) { - const { origin } = new URL(url); - return origin; - } + return new URL(url).origin; + }; return url; } catch (error) { + // Handling access errors, such as cross-domain restrictions logError(`Error accessing location: ${error}`); return ''; } @@ -31,12 +37,12 @@ export function getReferrer() { * @return {string} The modified URL with appended `vrref` or `fui` parameters. */ export function appendVrrefAndFui(url, domainName) { - const fullUrl = encodeURIComponent(getReferrer()); + const fullUrl = getCurrentUrl(); if (fullUrl) { return (url += '&vrref=' + getRelevantRefferer(domainName, fullUrl)); } url += '&fui=1'; // Full Url Issue - url += '&vrref=' + encodeURIComponent(domainName || ''); + if (domainName) url += '&vrref=' + encodeURIComponent(domainName); return url; } @@ -47,10 +53,9 @@ export function appendVrrefAndFui(url, domainName) { * @return {string} The relevant referrer */ export function getRelevantRefferer(domainName, fullUrl) { - if (domainName && isDomainIncluded(fullUrl, domainName)) { - return fullUrl; - } - return domainName ? encodeURIComponent(domainName) : fullUrl; + return encodeURIComponent( + domainName && isDomainIncluded(fullUrl, domainName) ? fullUrl : (domainName || fullUrl) + ); } /** @@ -61,7 +66,7 @@ export function getRelevantRefferer(domainName, fullUrl) { */ export function isDomainIncluded(fullUrl, domainName) { try { - return fullUrl.includes(domainName); + return new URL(fullUrl).hostname === domainName; } catch (error) { logError(`Invalid URL provided: ${error}`); return false; diff --git a/libraries/intentIqUtils/getUnitPosition.js b/libraries/intentIqUtils/getUnitPosition.js new file mode 100644 index 00000000000..025a06a9964 --- /dev/null +++ b/libraries/intentIqUtils/getUnitPosition.js @@ -0,0 +1,17 @@ +export function getUnitPosition(pbjs, adUnitCode) { + const adUnits = pbjs?.adUnits; + if (!Array.isArray(adUnits) || !adUnitCode) return; + + for (let i = 0; i < adUnits.length; i++) { + const adUnit = adUnits[i]; + if (adUnit?.code !== adUnitCode) continue; + + const mediaTypes = adUnit?.mediaTypes; + if (!mediaTypes || typeof mediaTypes !== 'object') return; + + const firstKey = Object.keys(mediaTypes)[0]; + const pos = mediaTypes[firstKey]?.pos; + + return typeof pos === 'number' ? pos : undefined; + } +} diff --git a/libraries/intentIqUtils/intentIqConfig.js b/libraries/intentIqUtils/intentIqConfig.js index 3f2572f14fa..41c731f646b 100644 --- a/libraries/intentIqUtils/intentIqConfig.js +++ b/libraries/intentIqUtils/intentIqConfig.js @@ -1,3 +1,33 @@ -export const iiqServerAddress = (configParams, gdprDetected) => typeof configParams?.iiqServerAddress === 'string' ? configParams.iiqServerAddress : gdprDetected ? 'https://api-gdpr.intentiq.com' : 'https://api.intentiq.com' -export const iiqPixelServerAddress = (configParams, gdprDetected) => typeof configParams?.iiqPixelServerAddress === 'string' ? configParams.iiqPixelServerAddress : gdprDetected ? 'https://sync-gdpr.intentiq.com' : 'https://sync.intentiq.com' -export const reportingServerAddress = (reportEndpoint, gdprDetected) => reportEndpoint && typeof reportEndpoint === 'string' ? reportEndpoint : gdprDetected ? 'https://reports-gdpr.intentiq.com/report' : 'https://reports.intentiq.com/report' +const REGION_MAPPING = { + gdpr: true, + apac: true, + emea: true +}; + +function checkRegion(region) { + if (typeof region !== 'string') return ''; + const lower = region.toLowerCase(); + return REGION_MAPPING[lower] ? lower : ''; +} + +function buildServerAddress(baseName, region) { + const checkedRegion = checkRegion(region); + if (checkedRegion) return `https://${baseName}-${checkedRegion}.intentiq.com`; + return `https://${baseName}.intentiq.com`; +} + +export const getIiqServerAddress = (configParams = {}) => { + if (typeof configParams?.iiqServerAddress === 'string') return configParams.iiqServerAddress; + return buildServerAddress('api', configParams.region); +}; + +export const iiqPixelServerAddress = (configParams = {}) => { + if (typeof configParams?.iiqPixelServerAddress === 'string') return configParams.iiqPixelServerAddress; + return buildServerAddress('sync', configParams.region); +}; + +export const reportingServerAddress = (reportEndpoint, region) => { + if (reportEndpoint && typeof reportEndpoint === 'string') return reportEndpoint; + const host = buildServerAddress('reports', region); + return `${host}/report`; +}; diff --git a/libraries/intentIqUtils/urlUtils.js b/libraries/intentIqUtils/urlUtils.js index 4cfb8273eab..78579aeb67d 100644 --- a/libraries/intentIqUtils/urlUtils.js +++ b/libraries/intentIqUtils/urlUtils.js @@ -1,5 +1,7 @@ -export function appendSPData (url, firstPartyData) { - const spdParam = firstPartyData?.spd ? encodeURIComponent(typeof firstPartyData.spd === 'object' ? JSON.stringify(firstPartyData.spd) : firstPartyData.spd) : ''; - url += spdParam ? '&spd=' + spdParam : ''; - return url +export function appendSPData (url, partnerData) { + const spdParam = partnerData?.spd ? encodeURIComponent(typeof partnerData.spd === 'object' ? JSON.stringify(partnerData.spd) : partnerData.spd) : ''; + if (!spdParam) { + return url; + } + return `${url}&spd=${spdParam}`; }; diff --git a/libraries/nativeAssetsUtils.js b/libraries/nativeAssetsUtils.js new file mode 100644 index 00000000000..9a59716cc68 --- /dev/null +++ b/libraries/nativeAssetsUtils.js @@ -0,0 +1,153 @@ +import { isEmpty } from '../src/utils.js'; + +export const NATIVE_PARAMS = { + title: { + id: 1, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + cta: { + id: 6, + type: 12, + name: 'data' + }, + body2: { + id: 7, + name: 'data', + type: 10 + }, + rating: { + id: 8, + name: 'data', + type: 3 + }, + likes: { + id: 9, + name: 'data', + type: 4 + }, + downloads: { + id: 10, + name: 'data', + type: 5 + }, + displayUrl: { + id: 11, + name: 'data', + type: 11 + }, + price: { + id: 12, + name: 'data', + type: 6 + }, + salePrice: { + id: 13, + name: 'data', + type: 7 + }, + address: { + id: 14, + name: 'data', + type: 9 + }, + phone: { + id: 15, + name: 'data', + type: 8 + } +}; + +const NATIVE_ID_MAP = Object.entries(NATIVE_PARAMS).reduce((result, [key, asset]) => { + result[asset.id] = key; + return result; +}, {}); + +export function buildNativeRequest(nativeParams) { + const assets = []; + if (nativeParams) { + Object.keys(nativeParams).forEach((key) => { + if (NATIVE_PARAMS[key]) { + const {name, type, id} = NATIVE_PARAMS[key]; + const assetObj = type ? {type} : {}; + let {len, sizes, required, aspect_ratios: aRatios} = nativeParams[key]; + if (len) { + assetObj.len = len; + } + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + const wmin = aRatios.min_width || 0; + const hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + assetObj.wmin = wmin; + assetObj.hmin = hmin; + } + if (sizes && sizes.length) { + sizes = [].concat(...sizes); + assetObj.w = sizes[0]; + assetObj.h = sizes[1]; + } + const asset = {required: required ? 1 : 0, id}; + asset[name] = assetObj; + assets.push(asset); + } + }); + } + return { + ver: '1.2', + request: { + assets: assets, + context: 1, + plcmttype: 1, + ver: '1.2' + } + }; +} + +export function parseNativeResponse(native) { + const {assets, link, imptrackers, jstracker} = native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [jstracker] : [] + }; + + (assets || []).forEach((asset) => { + const {id, img, data, title} = asset; + const key = NATIVE_ID_MAP[id]; + if (key) { + if (!isEmpty(title)) { + result.title = title.text; + } else if (!isEmpty(img)) { + result[key] = { + url: img.url, + height: img.h, + width: img.w + }; + } else if (!isEmpty(data)) { + result[key] = data.value; + } + } + }); + + return result; +} diff --git a/libraries/navigatorData/navigatorData.js b/libraries/navigatorData/navigatorData.js index f1a34fc51eb..e91a39493bf 100644 --- a/libraries/navigatorData/navigatorData.js +++ b/libraries/navigatorData/navigatorData.js @@ -7,23 +7,3 @@ export function getHLen(win = window) { } 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/objectGuard/objectGuard.js b/libraries/objectGuard/objectGuard.js index 78ba0910bb9..ff3f4b78c34 100644 --- a/libraries/objectGuard/objectGuard.js +++ b/libraries/objectGuard/objectGuard.js @@ -26,6 +26,10 @@ export function objectGuard(rules) { // build a tree representation of them, where the root is the object itself, // and each node's children are properties of the corresponding (nested) object. + function invalid() { + return new Error('incompatible redaction rules'); + } + rules.forEach(rule => { rule.paths.forEach(path => { let node = root; @@ -36,15 +40,22 @@ export function objectGuard(rules) { node.wpRules = node.wpRules ?? []; node.redactRules = node.redactRules ?? []; }); - (rule.wp ? node.wpRules : node.redactRules).push(rule); - if (rule.wp) { - // mark the whole path as write protected, so that write operations - // on parents do not need to walk down the tree - let parent = node; - while (parent && !parent.hasWP) { - parent.hasWP = true; - parent = parent.parent; + const tag = rule.wp ? 'hasWP' : 'hasRedact'; + const ruleset = rule.wp ? 'wpRules' : 'redactRules'; + // sanity check: do not allow rules of the same type on related paths, + // e.g. redact both 'user' and 'user.eids'; we don't need and this logic + // does not handle it + if (node[tag] && !node[ruleset]?.length) { + throw invalid(); + } + node[ruleset].push(rule); + let parent = node; + while (parent) { + parent[tag] = true; + if (parent !== node && parent[ruleset]?.length) { + throw invalid(); } + parent = parent.parent; } }); }); @@ -128,25 +139,57 @@ export function objectGuard(rules) { return true; } - function mkGuard(obj, tree, final, applies) { - return new Proxy(obj, { + const TARGET = Symbol('TARGET'); + + function mkGuard(obj, tree, final, applies, cache = new WeakMap()) { + // If this object is already proxied, return the cached proxy + if (cache.has(obj)) { + return cache.get(obj); + } + + /** + * Dereference (possibly nested) proxies to their underlying objects. + * + * This is to accommodate usage patterns like: + * + * guardedObject.property = [...guardedObject.property, additionalData]; + * + * where the `set` proxy trap would get an already proxied object as argument. + */ + function deref(obj, visited = new Set()) { + if (cache.has(obj?.[TARGET])) return obj[TARGET]; + if (obj == null || typeof obj !== 'object') return obj; + if (visited.has(obj)) return obj; + visited.add(obj); + Object.keys(obj).forEach(k => { + const sub = deref(obj[k], visited); + if (sub !== obj[k]) { + obj[k] = sub; + } + }) + return obj; + } + + const proxy = new Proxy(obj, { get(target, prop, receiver) { + if (prop === TARGET) return target; const val = Reflect.get(target, prop, receiver); if (final && val != null && typeof val === 'object') { // a parent property has write protect rules, keep guarding - return mkGuard(val, tree, final, applies) + return mkGuard(val, tree, final, applies, cache) } else if (tree.children?.hasOwnProperty(prop)) { const {children, hasWP} = tree.children[prop]; - if ((children || hasWP) && val != null && typeof val === 'object') { - // some nested properties have rules, return a guard for the branch - return mkGuard(val, tree.children?.[prop] || tree, final || children == null, applies); - } else if (isData(val)) { + if (isData(val)) { // if this property has redact rules, apply them const rule = getRedactRule(tree.children[prop]); if (rule && rule.check(applies)) { return rule.get(val); } } + if ((children || hasWP) && val != null && typeof val === 'object') { + // some nested properties have rules, return a guard for the branch + return mkGuard(val, tree.children?.[prop] || tree, final || children == null, applies, cache); + } } return val; }, @@ -158,11 +201,12 @@ export function objectGuard(rules) { return true; } } + newValue = deref(newValue); if (tree.children?.hasOwnProperty(prop)) { // apply all (possibly nested) write protect rules const curValue = Reflect.get(target, prop, receiver); newValue = cleanup(tree.children[prop], curValue, newValue, applies); - if (!isData(newValue) && !target.hasOwnProperty(prop)) { + if (typeof newValue === 'undefined' && !target.hasOwnProperty(prop)) { return true; } } @@ -183,6 +227,10 @@ export function objectGuard(rules) { return Reflect.deleteProperty(target, prop); } }); + + // Cache the proxy before returning + cache.set(obj, proxy); + return proxy; } return function guard(obj, ...args) { diff --git a/libraries/omsUtils/index.js b/libraries/omsUtils/index.js index b2523749080..733b257a4c8 100644 --- a/libraries/omsUtils/index.js +++ b/libraries/omsUtils/index.js @@ -1,4 +1,4 @@ -import {getWindowSelf, getWindowTop, isFn, isPlainObject} from '../../src/utils.js'; +import {createTrackPixelHtml, getWindowSelf, getWindowTop, isArray, isFn, isPlainObject} from '../../src/utils.js'; export function getBidFloor(bid) { if (!isFn(bid.getFloor)) { @@ -21,3 +21,28 @@ export function isIframe() { return true; } } + +export function getProcessedSizes(sizes = []) { + const bidSizes = ((isArray(sizes) && isArray(sizes[0])) ? sizes : [sizes]).filter(size => isArray(size)); + return bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); +} + +export function getDeviceType(ua = navigator.userAgent, sua) { + if (sua?.mobile || (/(ios|ipod|ipad|iphone|android)/i).test(ua)) { + return 1; + } + + if ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(ua)) { + return 3; + } + + return 2; +} + +export function getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} diff --git a/libraries/omsUtils/viewability.js b/libraries/omsUtils/viewability.js new file mode 100644 index 00000000000..ce62f74b841 --- /dev/null +++ b/libraries/omsUtils/viewability.js @@ -0,0 +1,19 @@ +import {getWindowTop} from '../../src/utils.js'; +import {percentInView} from '../percentInView/percentInView.js'; +import {getMinSize} from '../sizeUtils/sizeUtils.js'; +import {isIframe} from './index.js'; + +export function getRoundedViewability(adUnitCode, processedSizes) { + const element = document.getElementById(adUnitCode); + const minSize = getMinSize(processedSizes); + const viewabilityAmount = isViewabilityMeasurable(element) ? getViewability(element, minSize) : 'na'; + return isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); +} + +function isViewabilityMeasurable(element) { + return !isIframe() && element !== null; +} + +function getViewability(element, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, {w, h}) : 0; +} diff --git a/libraries/ortb2.5Translator/translator.js b/libraries/ortb2.5Translator/translator.js index 2fe6dcdf6e2..1634d6584d0 100644 --- a/libraries/ortb2.5Translator/translator.js +++ b/libraries/ortb2.5Translator/translator.js @@ -13,9 +13,18 @@ export const EXT_PROMOTIONS = [ export function splitPath(path) { const parts = path.split('.'); - const prefix = parts.slice(0, parts.length - 1).join('.'); - const field = parts[parts.length - 1]; - return [prefix, field]; + const field = parts.pop(); + return [parts.join('.'), field]; +} + +export function addExt(prefix, field) { + return `${prefix}.ext.${field}` +} + +function removeExt(prefix, field) { + const [newPrefix, ext] = splitPath(prefix); + if (ext !== 'ext') throw new Error('invalid argument'); + return `${newPrefix}.${field}`; } /** @@ -25,7 +34,7 @@ export function splitPath(path) { * @return {(function({}): (function(): void|undefined))|*} a function that takes an object and, if it contains * sourcePath, copies its contents to destinationPath, returning a function that deletes the original sourcePath. */ -export function moveRule(sourcePath, dest = (prefix, field) => `${prefix}.ext.${field}`) { +export function moveRule(sourcePath, dest) { const [prefix, field] = splitPath(sourcePath); dest = dest(prefix, field); return (ortb2) => { @@ -50,11 +59,15 @@ function kwarrayRule(section) { }; } -export const DEFAULT_RULES = Object.freeze([ - ...EXT_PROMOTIONS.map((f) => moveRule(f)), +export const TO_25_DEFAULT_RULES = Object.freeze([ + ...EXT_PROMOTIONS.map((f) => moveRule(f, addExt)), ...['app', 'content', 'site', 'user'].map(kwarrayRule) ]); +export const TO_26_DEFAULT_RULES = Object.freeze([ + ...EXT_PROMOTIONS.map(f => moveRule(addExt(...splitPath(f)), removeExt)), +]); + /** * Factory for ORTB 2.5 translation functions. * @@ -62,7 +75,7 @@ export const DEFAULT_RULES = Object.freeze([ * @param rules translation rules; an array of functions of the type returned by `moveRule` * @return {function({}): {}} a translation function that takes an ORTB object, modifies it in place, and returns it. */ -export function ortb25Translator(deleteFields = true, rules = DEFAULT_RULES) { +export function ortb25Translator(deleteFields = true, rules = TO_25_DEFAULT_RULES) { return function (ortb2) { rules.forEach(f => { try { @@ -82,3 +95,10 @@ export function ortb25Translator(deleteFields = true, rules = DEFAULT_RULES) { * The request is modified in place and returned. */ export const toOrtb25 = ortb25Translator(); + +/** + * Translate an ortb 2.5 request to version 2.6 by moving fields that have a standardized 2.5 extension. + * + * The request is modified in place and returned. + */ +export const toOrtb26 = ortb25Translator(true, TO_26_DEFAULT_RULES); diff --git a/libraries/ortbConverter/README.md b/libraries/ortbConverter/README.md index c67533ae1de..691ff7bceb7 100644 --- a/libraries/ortbConverter/README.md +++ b/libraries/ortbConverter/README.md @@ -378,7 +378,7 @@ For ease of use, the conversion logic gives special meaning to some context prop ## Prebid Server extensions -If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. +If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params, bidder aliases, targeting keys, and others. ```javascript import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js' diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index b1fb5be77a5..001d0d808bf 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -111,6 +111,9 @@ export const DEFAULT_PROCESSORS = { if (bid.ext?.eventtrackers) { bidResponse.eventtrackers = (bidResponse.eventtrackers ?? []).concat(bid.ext.eventtrackers); } + if (bid.cattax) { + bidResponse.meta.cattax = bid.cattax; + } } } } diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js index 3fa97ae674b..82a99954646 100644 --- a/libraries/pbsExtensions/processors/pbs.js +++ b/libraries/pbsExtensions/processors/pbs.js @@ -30,7 +30,7 @@ export const PBS_PROCESSORS = { }, [IMP]: { params: { - // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params, passed through transformBidParams if necessary + // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params fn: setImpBidParams }, adUnitCode: { diff --git a/libraries/percentInView/percentInView.js b/libraries/percentInView/percentInView.js index 27148e40941..5b9dfa10503 100644 --- a/libraries/percentInView/percentInView.js +++ b/libraries/percentInView/percentInView.js @@ -1,6 +1,29 @@ import { getWinDimensions, inIframe } from '../../src/utils.js'; import { getBoundingClientRect } from '../boundingClientRect/boundingClientRect.js'; +/** + * return the offset between the given window's viewport and the top window's. + */ +export function getViewportOffset(win = window) { + let x = 0; + let y = 0; + try { + while (win?.frameElement != null) { + const rect = getBoundingClientRect(win.frameElement); + x += rect.left; + y += rect.top; + win = win.parent; + } + } catch (e) { + // offset cannot be calculated as some parents are cross-frame + // fallback to 0,0 + x = 0; + y = 0; + } + + return {x, y}; +} + export function getBoundingBox(element, {w, h} = {}) { let {width, height, left, top, right, bottom, x, y} = getBoundingClientRect(element); @@ -44,14 +67,24 @@ function getIntersectionOfRects(rects) { export const percentInView = (element, {w, h} = {}) => { const elementBoundingBox = getBoundingBox(element, {w, h}); - const { innerHeight, innerWidth } = getWinDimensions(); + // when in an iframe, the bounding box is relative to the iframe's viewport + // since we are intersecting it with the top window's viewport, attempt to + // compensate for the offset between them + + const offset = getViewportOffset(element?.ownerDocument?.defaultView); + elementBoundingBox.left += offset.x; + elementBoundingBox.right += offset.x; + elementBoundingBox.top += offset.y; + elementBoundingBox.bottom += offset.y; + + const dims = getWinDimensions(); // Obtain the intersection of the element and the viewport const elementInViewBoundingBox = getIntersectionOfRects([{ left: 0, top: 0, - right: innerWidth, - bottom: innerHeight + right: dims.document.documentElement.clientWidth, + bottom: dims.document.documentElement.clientHeight }, elementBoundingBox]); let elementInViewArea, elementTotalArea; diff --git a/libraries/permutiveUtils/index.js b/libraries/permutiveUtils/index.js new file mode 100644 index 00000000000..ac410d6d662 --- /dev/null +++ b/libraries/permutiveUtils/index.js @@ -0,0 +1,34 @@ +import { deepAccess } from '../../src/utils.js' + +export const PERMUTIVE_VENDOR_ID = 361 + +/** + * Determine if required GDPR purposes are allowed, optionally requiring vendor consent. + * @param {Object} userConsent + * @param {number[]} requiredPurposes + * @param {boolean} enforceVendorConsent + * @returns {boolean} + */ +export function hasPurposeConsent(userConsent, requiredPurposes, enforceVendorConsent) { + const gdprApplies = deepAccess(userConsent, 'gdpr.gdprApplies') + if (!gdprApplies) return true + + if (enforceVendorConsent) { + const vendorConsents = deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') || {} + const vendorLegitimateInterests = deepAccess(userConsent, 'gdpr.vendorData.vendor.legitimateInterests') || {} + const purposeConsents = deepAccess(userConsent, 'gdpr.vendorData.purpose.consents') || {} + const purposeLegitimateInterests = deepAccess(userConsent, 'gdpr.vendorData.purpose.legitimateInterests') || {} + const hasVendorConsent = vendorConsents[PERMUTIVE_VENDOR_ID] === true || vendorLegitimateInterests[PERMUTIVE_VENDOR_ID] === true + + return hasVendorConsent && requiredPurposes.every((purposeId) => + purposeConsents[purposeId] === true || purposeLegitimateInterests[purposeId] === true + ) + } + + const purposeConsents = deepAccess(userConsent, 'gdpr.vendorData.publisher.consents') || {} + const purposeLegitimateInterests = deepAccess(userConsent, 'gdpr.vendorData.publisher.legitimateInterests') || {} + + return requiredPurposes.every((purposeId) => + purposeConsents[purposeId] === true || purposeLegitimateInterests[purposeId] === true + ) +} diff --git a/libraries/placementPositionInfo/placementPositionInfo.js b/libraries/placementPositionInfo/placementPositionInfo.js new file mode 100644 index 00000000000..19014632a23 --- /dev/null +++ b/libraries/placementPositionInfo/placementPositionInfo.js @@ -0,0 +1,87 @@ +import {getBoundingClientRect} from '../boundingClientRect/boundingClientRect.js'; +import {canAccessWindowTop, cleanObj, getWinDimensions, getWindowSelf, getWindowTop} from '../../src/utils.js'; +import {getViewability, getViewportOffset} from '../percentInView/percentInView.js'; + +export function getPlacementPositionUtils() { + const topWin = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); + const selfWin = getWindowSelf(); + + const getViewportHeight = () => { + const dim = getWinDimensions(); + return dim.innerHeight || dim.document.documentElement.clientHeight || dim.document.body.clientHeight || 0; + }; + + const getPageHeight = () => { + const dim = getWinDimensions(); + const body = dim.document.body; + const html = dim.document.documentElement; + if (!body || !html) return 0; + + return Math.max( + body.scrollHeight, + body.offsetHeight, + html.clientHeight, + html.scrollHeight, + html.offsetHeight + ); + }; + + const getViewableDistance = (element, frameOffset) => { + if (!element) return {distanceToView: 0, elementHeight: 0}; + + const elementRect = getBoundingClientRect(element); + if (!elementRect) return {distanceToView: 0, elementHeight: 0}; + + const elementTop = elementRect.top + frameOffset.y; + const elementBottom = elementRect.bottom + frameOffset.y; + const viewportHeight = getViewportHeight(); + + let distanceToView; + if (elementTop - viewportHeight <= 0 && elementBottom >= 0) { + distanceToView = 0; + } else if (elementTop - viewportHeight > 0) { + distanceToView = Math.round(elementTop - viewportHeight); + } else { + distanceToView = Math.round(elementBottom); + } + + return {distanceToView, elementHeight: elementRect.height}; + }; + + function getPlacementInfo(bidReq) { + const element = selfWin.document.getElementById(bidReq.adUnitCode); + const frameOffset = getViewportOffset(); + const {distanceToView, elementHeight} = getViewableDistance(element, frameOffset); + + const sizes = (bidReq.sizes || []).map(size => ({ + w: Number.parseInt(size[0], 10), + h: Number.parseInt(size[1], 10) + })); + const size = sizes.length > 0 + ? sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min, sizes[0]) + : {}; + + const placementPercentView = element ? getViewability(element, topWin, size) : 0; + + return cleanObj({ + AuctionsCount: bidReq.auctionsCount, + DistanceToView: distanceToView, + PlacementPercentView: Math.round(placementPercentView), + ElementHeight: Math.round(elementHeight) || 1 + }); + } + + function getPlacementEnv() { + return cleanObj({ + TimeFromNavigation: Math.floor(performance.now()), + TabActive: topWin.document.visibilityState === 'visible', + PageHeight: getPageHeight(), + ViewportHeight: getViewportHeight() + }); + } + + return { + getPlacementInfo, + getPlacementEnv + }; +} diff --git a/libraries/precisoUtils/bidUtils.js b/libraries/precisoUtils/bidUtils.js index 98ac87ad193..050f758601c 100644 --- a/libraries/precisoUtils/bidUtils.js +++ b/libraries/precisoUtils/bidUtils.js @@ -4,11 +4,12 @@ import { ajax } from '../../src/ajax.js'; // import { NATIVE } from '../../src/mediaTypes.js'; import { consentCheck, getBidFloor } from './bidUtilsCommon.js'; import { interpretNativeBid } from './bidNativeUtils.js'; +import { getTimeZone } from '../timezone/timezone.js'; export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest) => { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); logInfo('validBidRequests1 ::' + JSON.stringify(validBidRequests)); - var city = Intl.DateTimeFormat().resolvedOptions().timeZone; + const city = getTimeZone(); let req = { id: validBidRequests[0].auctionId, imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)), diff --git a/libraries/pubmaticUtils/plugins/dynamicTimeout.js b/libraries/pubmaticUtils/plugins/dynamicTimeout.js new file mode 100644 index 00000000000..ed8cdd52e9f --- /dev/null +++ b/libraries/pubmaticUtils/plugins/dynamicTimeout.js @@ -0,0 +1,209 @@ +import { logInfo } from '../../../src/utils.js'; +import { getGlobal } from '../../../src/prebidGlobal.js'; +import { bidderTimeoutFunctions } from '../../bidderTimeoutUtils/bidderTimeoutUtils.js'; +import { shouldThrottle } from '../pubmaticUtils.js'; + +let _dynamicTimeoutConfig = null; +export const getDynamicTimeoutConfig = () => _dynamicTimeoutConfig; +export const setDynamicTimeoutConfig = (config) => { _dynamicTimeoutConfig = config; } + +export const CONSTANTS = Object.freeze({ + LOG_PRE_FIX: 'PubMatic-Dynamic-Timeout: ', + INCLUDES_VIDEOS: 'includesVideo', + NUM_AD_UNITS: 'numAdUnits', + DEVICE_TYPE: 'deviceType', + CONNECTION_SPEED: 'connectionSpeed', + DEFAULT_SKIP_RATE: 50, + DEFAULT_THRESHOLD_TIMEOUT: 500 +}); + +export const RULES_PERCENTAGE = { + [CONSTANTS.INCLUDES_VIDEOS]: { + "true": 20, // 20% of bidderTimeout + "false": 5 // 5% of bidderTimeout + }, + [CONSTANTS.NUM_AD_UNITS]: { + "1-5": 10, // 10% of bidderTimeout + "6-10": 20, // 20% of bidderTimeout + "11-15": 30 // 30% of bidderTimeout + }, + [CONSTANTS.DEVICE_TYPE]: { + "2": 5, // 5% of bidderTimeout + "4": 10, // 10% of bidderTimeout + "5": 20 // 20% of bidderTimeout + }, + [CONSTANTS.CONNECTION_SPEED]: { + "slow": 20, // 20% of bidderTimeout + "medium": 10, // 10% of bidderTimeout + "fast": 5, // 5% of bidderTimeout + "unknown": 1 // 1% of bidderTimeout + } +}; + +/** + * Initialize the dynamic timeout plugin + * @param {Object} pluginName - Plugin name + * @param {Object} configJsonManager - Configuration JSON manager object + * @returns {Promise} - Promise resolving to initialization status + */ +export async function init(pluginName, configJsonManager) { + const config = configJsonManager.getConfigByName(pluginName); + if (!config) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Dynamic Timeout configuration not found`); + return false; + } + // Set the Dynamic Timeout config + setDynamicTimeoutConfig(config); + + if (!getDynamicTimeoutConfig()?.enabled) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Dynamic Timeout configuration is disabled`); + return false; + } + return true; +} + +/** + * Process bid request by applying dynamic timeout adjustments + * @param {Object} reqBidsConfigObj - Bid request config object + * @returns {Object} - Updated bid request config object with adjusted timeout + */ +export function processBidRequest(reqBidsConfigObj) { + // Cache config to avoid multiple calls + const timeoutConfig = getDynamicTimeoutConfig(); + + // Check if request should be throttled based on skipRate + const skipRate = (timeoutConfig?.config?.skipRate !== undefined && timeoutConfig?.config?.skipRate !== null) ? timeoutConfig?.config?.skipRate : CONSTANTS.DEFAULT_SKIP_RATE; + if (shouldThrottle(skipRate)) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Dynamic timeout is skipped (skipRate: ${skipRate}%)`); + return reqBidsConfigObj; + } + + logInfo(`${CONSTANTS.LOG_PRE_FIX} Dynamic timeout is applying...`); + + // Get ad units and bidder timeout + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const bidderTimeout = getBidderTimeout(reqBidsConfigObj); + + // Calculate and apply additional timeout + const rules = getRules(bidderTimeout); + const additionalTimeout = bidderTimeoutFunctions.calculateTimeoutModifier(adUnits, rules); + + reqBidsConfigObj.timeout = getFinalTimeout(bidderTimeout, additionalTimeout); + + logInfo(`${CONSTANTS.LOG_PRE_FIX} Timeout adjusted from ${bidderTimeout}ms to ${reqBidsConfigObj.timeout}ms (added ${additionalTimeout}ms)`); + return reqBidsConfigObj; +} + +/** + * Get targeting data + * @param {Array} adUnitCodes - Ad unit codes + * @param {Object} config - Module configuration + * @param {Object} userConsent - User consent data + * @param {Object} auction - Auction object + * @returns {Object} - Targeting data + */ +export function getTargeting(adUnitCodes, config, userConsent, auction) { + // Implementation for targeting data, if not applied then do nothing +} + +// Export the dynamic timeout functions +export const DynamicTimeout = { + init, + processBidRequest, + getTargeting +}; + +// Helper Functions + +export const getFinalTimeout = (bidderTimeout, additionalTimeout) => { + // Calculate the final timeout by adding bidder timeout and additional timeout + const calculatedTimeout = parseInt(bidderTimeout) + parseInt(additionalTimeout); + const thresholdTimeout = getDynamicTimeoutConfig()?.config?.thresholdTimeout || CONSTANTS.DEFAULT_THRESHOLD_TIMEOUT; + + // Handle cases where the calculated timeout might be negative or below threshold + if (calculatedTimeout < thresholdTimeout) { + // Log warning for negative or very low timeouts + if (calculatedTimeout < 0) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Warning: Negative timeout calculated (${calculatedTimeout}ms), using threshold (${thresholdTimeout}ms)`); + } else if (calculatedTimeout < thresholdTimeout) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Calculated timeout (${calculatedTimeout}ms) below threshold, using threshold (${thresholdTimeout}ms)`); + } + return thresholdTimeout; + } + + return calculatedTimeout; +} + +export const getBidderTimeout = (reqBidsConfigObj) => { + return getDynamicTimeoutConfig()?.config?.bidderTimeout + ? getDynamicTimeoutConfig()?.config?.bidderTimeout + : reqBidsConfigObj?.timeout || getGlobal()?.getConfig('bidderTimeout'); +} + +/** + * Get rules based on percentage values and bidderTimeout + * @param {number} bidderTimeout - Bidder timeout in milliseconds + * @returns {Object} - Rules with calculated millisecond values + */ +export const getRules = (bidderTimeout) => { + const timeoutConfig = getDynamicTimeoutConfig(); + + // In milliseconds - If timeout rules provided by publishers are available then return it + if (timeoutConfig?.config?.timeoutRules && Object.keys(timeoutConfig.config.timeoutRules).length > 0) { + return timeoutConfig.config.timeoutRules; + } + // In milliseconds - Check for rules in priority order, If ML model rules are available then return it + if (timeoutConfig?.data && Object.keys(timeoutConfig.data).length > 0) { + return timeoutConfig.data; + } + // In Percentage - If no rules are available then create rules from the default defined - values are in percentages + return createDynamicRules(RULES_PERCENTAGE, bidderTimeout); +} + +/** + * Creates dynamic rules based on percentage values and bidder timeout + * @param {Object} percentageRules - Rules with percentage values + * @param {number} bidderTimeout - Bidder timeout in milliseconds + * @return {Object} - Rules with calculated millisecond values + */ +export const createDynamicRules = (percentageRules, bidderTimeout) => { + // Return empty object if required parameters are missing or invalid + if (!percentageRules || typeof percentageRules !== 'object') { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Invalid percentage rules provided to createDynamicRules`); + return {}; + } + + // Handle negative or zero bidderTimeout gracefully + if (!bidderTimeout || typeof bidderTimeout !== 'number' || bidderTimeout <= 0) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Invalid bidderTimeout (${bidderTimeout}ms) provided to createDynamicRules`); + return {}; + } + + // Create a new rules object with millisecond values + return Object.entries(percentageRules).reduce((dynamicRules, [category, rules]) => { + // Skip if rules is not an object + if (!rules || typeof rules !== 'object') { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Skipping invalid rule category: ${category}`); + return dynamicRules; + } + + // Initialize category in the dynamic rules + dynamicRules[category] = {}; + + // Convert each percentage value to milliseconds + Object.entries(rules).forEach(([key, percentValue]) => { + // Ensure percentage value is a number and not zero + if (typeof percentValue === 'number' && percentValue !== 0) { + const calculatedTimeout = Math.floor(bidderTimeout * (percentValue / 100)); + dynamicRules[category][key] = calculatedTimeout; + + // Log warning for negative calculated timeouts + if (calculatedTimeout < 0) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Warning: Negative timeout calculated for ${category}.${key}: ${calculatedTimeout}ms`); + } + } + }); + + return dynamicRules; + }, {}); +}; diff --git a/libraries/pubmaticUtils/plugins/floorProvider.js b/libraries/pubmaticUtils/plugins/floorProvider.js new file mode 100644 index 00000000000..a6112634353 --- /dev/null +++ b/libraries/pubmaticUtils/plugins/floorProvider.js @@ -0,0 +1,167 @@ +// plugins/floorProvider.js +import { logInfo, logError, logMessage, isEmpty } from '../../../src/utils.js'; +import { getDeviceType as fetchDeviceType, getOS } from '../../userAgentUtils/index.js'; +import { getBrowserType, getCurrentTimeOfDay, getUtmValue, getDayOfWeek, getHourOfDay } from '../pubmaticUtils.js'; +import { config as conf } from '../../../src/config.js'; + +/** + * This RTD module has a dependency on the priceFloors module. + * We utilize the continueAuction function from the priceFloors module to incorporate price floors data into the current auction. + */ +import { continueAuction } from '../../../modules/priceFloors.js'; // eslint-disable-line prebid/validate-imports + +let _floorConfig = null; +export const getFloorConfig = () => _floorConfig; +export const setFloorsConfig = (config) => { _floorConfig = config; } + +let _configJsonManager = null; +export const getConfigJsonManager = () => _configJsonManager; +export const setConfigJsonManager = (configJsonManager) => { _configJsonManager = configJsonManager; } + +export const CONSTANTS = Object.freeze({ + LOG_PRE_FIX: 'PubMatic-Floor-Provider: ' +}); + +/** + * Initialize the floor provider + * @param {Object} pluginName - Plugin name + * @param {Object} configJsonManager - Configuration JSON manager object + * @returns {Promise} - Promise resolving to initialization status + */ +export async function init(pluginName, configJsonManager) { + // Process floor-specific configuration + const config = configJsonManager.getConfigByName(pluginName); + if (!config) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Floor configuration not found`); + return false; + } + setFloorsConfig(config); + + if (!getFloorConfig()?.enabled) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Floor configuration is disabled`); + return false; + } + + setConfigJsonManager(configJsonManager); + try { + conf.setConfig(prepareFloorsConfig()); + logMessage(`${CONSTANTS.LOG_PRE_FIX} dynamicFloors config set successfully`); + } catch (error) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error setting dynamicFloors config: ${error}`); + } + + logInfo(`${CONSTANTS.LOG_PRE_FIX} Floor configuration loaded`); + + return true; +} + +/** + * Process bid request + * @param {Object} reqBidsConfigObj - Bid request config object + * @returns {Object} - Updated bid request config object + */ +export function processBidRequest(reqBidsConfigObj) { + try { + const hookConfig = { + reqBidsConfigObj, + context: null, // Removed 'this' as it's not applicable in function-based implementation + nextFn: () => true, + haveExited: false, + timer: null + }; + + // Apply floor configuration + continueAuction(hookConfig); + logInfo(`${CONSTANTS.LOG_PRE_FIX} Applied floor configuration to auction`); + + return reqBidsConfigObj; + } catch (error) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error applying floor configuration: ${error}`); + return reqBidsConfigObj; + } +} + +/** + * Get targeting data + * @param {Array} adUnitCodes - Ad unit codes + * @param {Object} config - Module configuration + * @param {Object} userConsent - User consent data + * @param {Object} auction - Auction object + * @returns {Object} - Targeting data + */ +export function getTargeting(adUnitCodes, config, userConsent, auction) { + // Implementation for targeting data, if not applied then do nothing +} + +// Export the floor provider functions +export const FloorProvider = { + init, + processBidRequest, + getTargeting +}; + +// Helper Functions + +export const defaultValueTemplate = { + currency: 'USD', + skipRate: 0, + schema: { + fields: ['mediaType', 'size'] + } +}; + +// Getter Functions +export const getTimeOfDay = () => getCurrentTimeOfDay(); +export const getBrowser = () => getBrowserType(); +export const getOs = () => getOS().toString(); +export const getDeviceType = () => fetchDeviceType().toString(); +export const getCountry = () => getConfigJsonManager().country; +export const getBidder = (request) => request?.bidder; +export const getUtm = () => getUtmValue(); +export const getDOW = () => getDayOfWeek(); +export const getHOD = () => getHourOfDay(); + +export const prepareFloorsConfig = () => { + if (!getFloorConfig()?.enabled || !getFloorConfig()?.config) { + return undefined; + } + + // Floor configs from adunit / setconfig + const defaultFloorConfig = conf.getConfig('floors') ?? {}; + if (defaultFloorConfig?.endpoint) { + delete defaultFloorConfig.endpoint; + } + + let ymUiConfig = { ...getFloorConfig().config }; + + // default values provided by publisher on YM UI + const defaultValues = ymUiConfig.defaultValues ?? {}; + // If floorsData is not present or is an empty object, use default values + const ymFloorsData = isEmpty(getFloorConfig().data) + ? { ...defaultValueTemplate, values: { ...defaultValues } } + : getFloorConfig().data; + + delete ymUiConfig.defaultValues; + // If skiprate is provided in configs, overwrite the value in ymFloorsData + (ymUiConfig.skipRate !== undefined) && (ymFloorsData.skipRate = ymUiConfig.skipRate); + + // merge default configs from page, configs + return { + floors: { + ...defaultFloorConfig, + ...ymUiConfig, + data: ymFloorsData, + additionalSchemaFields: { + deviceType: getDeviceType, + timeOfDay: getTimeOfDay, + browser: getBrowser, + os: getOs, + utm: getUtm, + country: getCountry, + bidder: getBidder, + dayOfWeek: getDOW, + hourOfDay: getHOD + }, + }, + }; +}; diff --git a/libraries/pubmaticUtils/plugins/pluginManager.js b/libraries/pubmaticUtils/plugins/pluginManager.js new file mode 100644 index 00000000000..d9f955a9fe2 --- /dev/null +++ b/libraries/pubmaticUtils/plugins/pluginManager.js @@ -0,0 +1,106 @@ +import { logInfo, logWarn, logError } from "../../../src/utils.js"; + +// pluginManager.js +export const plugins = new Map(); +export const CONSTANTS = Object.freeze({ + LOG_PRE_FIX: 'PubMatic-Plugin-Manager: ' +}); + +/** + * Initialize the plugin manager with constants + * @returns {Object} - Plugin manager functions + */ +export const PluginManager = () => ({ + register, + initialize, + executeHook +}); + +/** + * Register a plugin with the plugin manager + * @param {string} name - Plugin name + * @param {Object} plugin - Plugin object + * @returns {Object} - Plugin manager functions + */ +const register = (name, plugin) => { + if (plugins.has(name)) { + logWarn(`${CONSTANTS.LOG_PRE_FIX} Plugin ${name} already registered`); + return; + } + plugins.set(name, plugin); +}; + +/** + * Unregister a plugin from the plugin manager + * @param {string} name - Plugin name + * @returns {Object} - Plugin manager functions + */ +const unregister = (name) => { + if (plugins.has(name)) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin ${name}`); + plugins.delete(name); + } +}; + +/** + * Initialize all registered plugins with their specific config + * @param {Object} configJsonManager - Configuration JSON manager object + * @returns {Promise} - Promise resolving when all plugins are initialized + */ +const initialize = async (configJsonManager) => { + const initPromises = []; + + // Initialize each plugin with its specific config + for (const [name, plugin] of plugins.entries()) { + if (plugin.init) { + const initialized = await plugin.init(name, configJsonManager); + if (!initialized) { + unregister(name); + } + initPromises.push(initialized); + } + } + + return Promise.all(initPromises); +}; + +/** + * Execute a hook on all registered plugins synchronously + * @param {string} hookName - Name of the hook to execute + * @param {...any} args - Arguments to pass to the hook + * @returns {Object} - Object containing merged results from all plugins + */ +const executeHook = (hookName, ...args) => { + // Cache results to avoid repeated processing + const results = {}; + + try { + // Get all plugins that have the specified hook method + const pluginsWithHook = Array.from(plugins.entries()) + .filter(([_, plugin]) => typeof plugin[hookName] === 'function'); + + // Process each plugin synchronously + for (const [name, plugin] of pluginsWithHook) { + try { + // Call the plugin's hook method synchronously + const result = plugin[hookName](...args); + + // Skip null/undefined results + if (result === null || result === undefined) { + continue; + } + + // If result is an object, merge it + if (typeof result === 'object') { + Object.assign(results, result); + } + } catch (error) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error executing hook ${hookName} in plugin ${name}: ${error.message}`); + } + } + } catch (error) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error in executeHookSync: ${error.message}`); + } + + return results; +}; diff --git a/libraries/pubmaticUtils/plugins/unifiedPricingRule.js b/libraries/pubmaticUtils/plugins/unifiedPricingRule.js new file mode 100644 index 00000000000..4060af17044 --- /dev/null +++ b/libraries/pubmaticUtils/plugins/unifiedPricingRule.js @@ -0,0 +1,375 @@ +// plugins/unifiedPricingRule.js +import { logError, logInfo } from '../../../src/utils.js'; +import { getGlobal } from '../../../src/prebidGlobal.js'; +import { REJECTION_REASON } from '../../../src/constants.js'; + +const CONSTANTS = Object.freeze({ + LOG_PRE_FIX: 'PubMatic-Unified-Pricing-Rule: ', + BID_STATUS: { + NOBID: 0, + WON: 1, + FLOORED: 2 + }, + MULTIPLIERS: { + WIN: 1.0, + FLOORED: 1.0, + NOBID: 1.0 + }, + TARGETING_KEYS: { + PM_YM_FLRS: 'pm_ym_flrs', // Whether RTD floor was applied + PM_YM_FLRV: 'pm_ym_flrv', // Final floor value (after applying multiplier) + PM_YM_BID_S: 'pm_ym_bid_s' // Bid status (0: No bid, 1: Won, 2: Floored) + } +}); +export const getProfileConfigs = () => getConfigJsonManager()?.getYMConfig(); + +let _configJsonManager = null; +export const getConfigJsonManager = () => _configJsonManager; +export const setConfigJsonManager = (configJsonManager) => { _configJsonManager = configJsonManager; } + +/** + * Initialize the floor provider + * @param {Object} pluginName - Plugin name + * @param {Object} configJsonManager - Configuration JSON manager object + * @returns {Promise} - Promise resolving to initialization status + */ +export async function init(pluginName, configJsonManager) { + setConfigJsonManager(configJsonManager); + return true; +} + +/** + * Process bid request + * @param {Object} reqBidsConfigObj - Bid request config object + * @returns {Object} - Updated bid request config object + */ +export function processBidRequest(reqBidsConfigObj) { + return reqBidsConfigObj; +} + +/** + * Get targeting data + * @param {Array} adUnitCodes - Ad unit codes + * @param {Object} config - Module configuration + * @param {Object} userConsent - User consent data + * @param {Object} auction - Auction object + * @returns {Object} - Targeting data + */ +export function getTargeting(adUnitCodes, config, userConsent, auction) { + // Access the profile configs stored globally + const profileConfigs = getProfileConfigs(); + + // Return empty object if profileConfigs is undefined or pmTargetingKeys.enabled is explicitly set to false + if (!profileConfigs || profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.enabled === false) { + logInfo(`${CONSTANTS.LOG_PRE_FIX} pmTargetingKeys is disabled or profileConfigs is undefined`); + return {}; + } + + // Helper to check if RTD floor is applied to a bid + const isRtdFloorApplied = bid => bid.floorData?.floorProvider === "PM" && !bid.floorData.skipped; + + // Check if any bid has RTD floor applied + const hasRtdFloorAppliedBid = + auction?.adUnits?.some(adUnit => adUnit.bids?.some(isRtdFloorApplied)) || + auction?.bidsReceived?.some(isRtdFloorApplied); + + // Only log when RTD floor is applied + if (hasRtdFloorAppliedBid) { + logInfo(CONSTANTS.LOG_PRE_FIX, 'Setting targeting via getTargetingData:'); + } + + // Process each ad unit code + const targeting = {}; + + adUnitCodes.forEach(code => { + targeting[code] = {}; + + // For non-RTD floor applied cases, only set pm_ym_flrs to 0 + if (!hasRtdFloorAppliedBid) { + targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 0; + return; + } + + // Find bids and determine status for RTD floor applied cases + const bidsForAdUnit = findBidsForAdUnit(auction, code); + const rejectedBidsForAdUnit = findRejectedBidsForAdUnit(auction, code); + const rejectedFloorBid = findRejectedFloorBid(rejectedBidsForAdUnit); + const winningBid = findWinningBid(code); + + // Determine bid status and values + const { bidStatus, baseValue, multiplier } = determineBidStatusAndValues( + winningBid, + rejectedFloorBid, + bidsForAdUnit, + auction, + code + ); + + // Set all targeting keys + targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 1; + targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRV] = (baseValue * multiplier).toFixed(2); + targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_BID_S] = bidStatus; + }); + + return targeting; +} + +// Export the floor provider functions +export const UnifiedPricingRule = { + init, + processBidRequest, + getTargeting +}; + +// Find all bids for a specific ad unit +function findBidsForAdUnit(auction, code) { + return auction?.bidsReceived?.filter(bid => bid.adUnitCode === code) || []; +} + +// Find rejected bids for a specific ad unit +function findRejectedBidsForAdUnit(auction, code) { + if (!auction?.bidsRejected) return []; + + // If bidsRejected is an array + if (Array.isArray(auction.bidsRejected)) { + return auction.bidsRejected.filter(bid => bid.adUnitCode === code); + } + + // If bidsRejected is an object mapping bidders to their rejected bids + if (typeof auction.bidsRejected === 'object') { + return Object.values(auction.bidsRejected) + .filter(Array.isArray) + .flatMap(bidderBids => bidderBids.filter(bid => bid.adUnitCode === code)); + } + + return []; +} + +// Find a rejected bid due to price floor +function findRejectedFloorBid(rejectedBids) { + return rejectedBids.find(bid => { + return bid.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET && + (bid.floorData?.floorValue && bid.cpm < bid.floorData.floorValue); + }); +} + +// Find the winning or highest bid for an ad unit +function findWinningBid(adUnitCode) { + try { + const pbjs = getGlobal(); + if (!pbjs?.getHighestCpmBids) return null; + + const highestCpmBids = pbjs.getHighestCpmBids(adUnitCode); + if (!highestCpmBids?.length) { + logInfo(CONSTANTS.LOG_PRE_FIX, `No highest CPM bids found for ad unit: ${adUnitCode}`); + return null; + } + + const highestCpmBid = highestCpmBids[0]; + logInfo(CONSTANTS.LOG_PRE_FIX, `Found highest CPM bid using pbjs.getHighestCpmBids() for ad unit: ${adUnitCode}, CPM: ${highestCpmBid.cpm}`); + return highestCpmBid; + } catch (error) { + logError(CONSTANTS.LOG_PRE_FIX, `Error finding highest CPM bid: ${error}`); + return null; + } +} + +// Find floor value from bidder requests +function findFloorValueFromBidderRequests(auction, code) { + if (!auction?.bidderRequests?.length) return 0; + + // Find all bids in bidder requests for this ad unit + const bidsFromRequests = auction.bidderRequests + .flatMap(request => request.bids || []) + .filter(bid => bid.adUnitCode === code); + + if (!bidsFromRequests.length) { + logInfo(CONSTANTS.LOG_PRE_FIX, `No bids found for ad unit: ${code}`); + return 0; + } + + const bidWithGetFloor = bidsFromRequests.find(bid => bid.getFloor); + if (!bidWithGetFloor) { + logInfo(CONSTANTS.LOG_PRE_FIX, `No bid with getFloor method found for ad unit: ${code}`); + return 0; + } + + // Helper function to extract sizes with their media types from a source object + const extractSizes = (source) => { + if (!source) return null; + + const result = []; + + // Extract banner sizes + if (source.mediaTypes?.banner?.sizes) { + source.mediaTypes.banner.sizes.forEach(size => { + result.push({ + size, + mediaType: 'banner' + }); + }); + } + + // Extract video sizes + if (source.mediaTypes?.video?.playerSize) { + const playerSize = source.mediaTypes.video.playerSize; + // Handle both formats: [[w, h]] and [w, h] + const videoSizes = Array.isArray(playerSize[0]) ? playerSize : [playerSize]; + + videoSizes.forEach(size => { + result.push({ + size, + mediaType: 'video' + }); + }); + } + + // Use general sizes as fallback if no specific media types found + if (result.length === 0 && source.sizes) { + source.sizes.forEach(size => { + result.push({ + size, + mediaType: 'banner' // Default to banner for general sizes + }); + }); + } + + return result.length > 0 ? result : null; + }; + + // Try to get sizes from different sources in order of preference + const adUnit = auction.adUnits?.find(unit => unit.code === code); + let sizes = extractSizes(adUnit) || extractSizes(bidWithGetFloor); + + // Handle fallback to wildcard size if no sizes found + if (!sizes) { + sizes = [{ size: ['*', '*'], mediaType: 'banner' }]; + logInfo(CONSTANTS.LOG_PRE_FIX, `No sizes found, using wildcard size for ad unit: ${code}`); + } + + // Try to get floor values for each size + let minFloor = -1; + + for (const sizeObj of sizes) { + // Extract size and mediaType from the object + const { size, mediaType } = sizeObj; + + // Call getFloor with the appropriate media type + const floorInfo = bidWithGetFloor.getFloor({ + currency: 'USD', // Default currency + mediaType: mediaType, // Use the media type we extracted + size: size + }); + + if (floorInfo?.floor && !isNaN(parseFloat(floorInfo.floor))) { + const floorValue = parseFloat(floorInfo.floor); + logInfo(CONSTANTS.LOG_PRE_FIX, `Floor value for ${mediaType} size ${size}: ${floorValue}`); + + // Update minimum floor value + minFloor = minFloor === -1 ? floorValue : Math.min(minFloor, floorValue); + } + } + + if (minFloor !== -1) { + logInfo(CONSTANTS.LOG_PRE_FIX, `Calculated minimum floor value ${minFloor} for ad unit: ${code}`); + return minFloor; + } + + logInfo(CONSTANTS.LOG_PRE_FIX, `No floor data found for ad unit: ${code}`); + return 0; +} + +// Select multiplier based on priority order: floors.json → config.json → default +function selectMultiplier(multiplierKey, profileConfigs) { + // Define sources in priority order + const multiplierSources = [ + { + name: 'config.json', + getValue: () => { + const configPath = profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.multiplier; + const lowerKey = multiplierKey.toLowerCase(); + return configPath && lowerKey in configPath ? configPath[lowerKey] : null; + } + }, + { + name: 'floor.json', + getValue: () => { + const configPath = profileConfigs?.plugins?.dynamicFloors?.data?.multiplier; + const lowerKey = multiplierKey.toLowerCase(); + return configPath && lowerKey in configPath ? configPath[lowerKey] : null; + } + }, + { + name: 'default', + getValue: () => CONSTANTS.MULTIPLIERS[multiplierKey] + } + ]; + + // Find the first source with a non-null value + for (const source of multiplierSources) { + const value = source.getValue(); + if (value != null) { + return { value, source: source.name }; + } + } + + // Fallback (shouldn't happen due to default source) + return { value: CONSTANTS.MULTIPLIERS[multiplierKey], source: 'default' }; +} + +// Identify winning bid scenario and return scenario data +function handleWinningBidScenario(winningBid, code) { + return { + scenario: 'winning', + bidStatus: CONSTANTS.BID_STATUS.WON, + baseValue: winningBid.cpm, + multiplierKey: 'WIN', + logMessage: `Bid won for ad unit: ${code}, CPM: ${winningBid.cpm}` + }; +} + +// Identify rejected floor bid scenario and return scenario data +function handleRejectedFloorBidScenario(rejectedFloorBid, code) { + const baseValue = rejectedFloorBid.floorData?.floorValue || 0; + return { + scenario: 'rejected', + bidStatus: CONSTANTS.BID_STATUS.FLOORED, + baseValue, + multiplierKey: 'FLOORED', + logMessage: `Bid rejected due to price floor for ad unit: ${code}, Floor value: ${baseValue}, Bid CPM: ${rejectedFloorBid.cpm}` + }; +} + +// Identify no bid scenario and return scenario data +function handleNoBidScenario(auction, code) { + const baseValue = findFloorValueFromBidderRequests(auction, code); + return { + scenario: 'nobid', + bidStatus: CONSTANTS.BID_STATUS.NOBID, + baseValue, + multiplierKey: 'NOBID', + logMessage: `No bids for ad unit: ${code}, Floor value: ${baseValue}` + }; +} + +// Determine which scenario applies based on bid conditions +function determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) { + return winningBid ? handleWinningBidScenario(winningBid, code) + : rejectedFloorBid ? handleRejectedFloorBidScenario(rejectedFloorBid, code) + : handleNoBidScenario(auction, code); +} + +// Main function that determines bid status and calculates values +function determineBidStatusAndValues(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) { + const profileConfigs = getProfileConfigs(); + + // Determine the scenario based on bid conditions + const { bidStatus, baseValue, multiplierKey, logMessage } = + determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code); + + // Select the appropriate multiplier + const { value: multiplier, source } = selectMultiplier(multiplierKey, profileConfigs); + logInfo(CONSTANTS.LOG_PRE_FIX, logMessage + ` (Using ${source} multiplier: ${multiplier})`); + + return { bidStatus, baseValue, multiplier }; +} diff --git a/libraries/pubmaticUtils/pubmaticUtils.js b/libraries/pubmaticUtils/pubmaticUtils.js new file mode 100644 index 00000000000..a7e23f1ee76 --- /dev/null +++ b/libraries/pubmaticUtils/pubmaticUtils.js @@ -0,0 +1,86 @@ +import { getLowEntropySUA } from '../../src/fpd/sua.js'; + +const CONSTANTS = Object.freeze({ + TIME_OF_DAY_VALUES: { + MORNING: 'morning', + AFTERNOON: 'afternoon', + EVENING: 'evening', + NIGHT: 'night' + }, + UTM: 'utm_', + UTM_VALUES: { + TRUE: '1', + FALSE: '0' + }, +}); + +const BROWSER_REGEX_MAP = [ + { regex: /\b(?:crios)\/([\w.]+)/i, id: 1 }, // Chrome for iOS + { regex: /(edg|edge)(?:e|ios|a)?(?:\/([\w.]+))?/i, id: 2 }, // Edge + { regex: /(opera|opr)(?:.+version\/|\/|\s+)([\w.]+)/i, id: 3 }, // Opera + { regex: /(?:ms|\()(ie) ([\w.]+)|(?:trident\/[\w.]+)/i, id: 4 }, // Internet Explorer + { regex: /fxios\/([-\w.]+)/i, id: 5 }, // Firefox for iOS + { regex: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w.]+);)/i, id: 6 }, // Facebook In-App Browser + { regex: / wv\).+(chrome)\/([\w.]+)/i, id: 7 }, // Chrome WebView + { regex: /droid.+ version\/([\w.]+)\b.+(?:mobile safari|safari)/i, id: 8 }, // Android Browser + { regex: /(chrome|crios)(?:\/v?([\w.]+))?\b/i, id: 9 }, // Chrome + { regex: /version\/([\w.,]+) .*mobile\/\w+ (safari)/i, id: 10 }, // Safari Mobile + { regex: /version\/([\w.,]+) .*(mobile ?safari|safari)/i, id: 11 }, // Safari + { regex: /(firefox)\/([\w.]+)/i, id: 12 } // Firefox +]; + +export const getBrowserType = () => { + const brandName = getLowEntropySUA()?.browsers + ?.map(b => b.brand.toLowerCase()) + .join(' ') || ''; + const browserMatch = brandName ? BROWSER_REGEX_MAP.find(({ regex }) => regex.test(brandName)) : -1; + + if (browserMatch?.id) return browserMatch.id.toString(); + + const userAgent = navigator?.userAgent; + let browserIndex = userAgent == null ? -1 : 0; + + if (userAgent) { + browserIndex = BROWSER_REGEX_MAP.find(({ regex }) => regex.test(userAgent))?.id || 0; + } + return browserIndex.toString(); +} + +export const getCurrentTimeOfDay = () => { + const currentHour = new Date().getHours(); + + return currentHour < 5 ? CONSTANTS.TIME_OF_DAY_VALUES.NIGHT + : currentHour < 12 ? CONSTANTS.TIME_OF_DAY_VALUES.MORNING + : currentHour < 17 ? CONSTANTS.TIME_OF_DAY_VALUES.AFTERNOON + : currentHour < 19 ? CONSTANTS.TIME_OF_DAY_VALUES.EVENING + : CONSTANTS.TIME_OF_DAY_VALUES.NIGHT; +} + +export const getUtmValue = () => { + const url = new URL(window.location?.href); + const urlParams = new URLSearchParams(url?.search); + return urlParams && urlParams.toString().includes(CONSTANTS.UTM) ? CONSTANTS.UTM_VALUES.TRUE : CONSTANTS.UTM_VALUES.FALSE; +} + +export const getDayOfWeek = () => { + const dayOfWeek = new Date().getDay(); + return dayOfWeek.toString(); +} + +export const getHourOfDay = () => { + const hourOfDay = new Date().getHours(); + return hourOfDay.toString(); +} + +/** + * Determines whether an action should be throttled based on a given percentage. + * + * @param {number} skipRate - The percentage rate at which throttling will be applied (0-100). + * @param {number} maxRandomValue - The upper bound for generating a random number (default is 100). + * @returns {boolean} - Returns true if the action should be throttled, false otherwise. + */ +export const shouldThrottle = (skipRate, maxRandomValue = 100) => { + // Determine throttling based on the throttle rate and a random value + const rate = skipRate ?? maxRandomValue; + return Math.floor(Math.random() * maxRandomValue) < rate; +}; diff --git a/libraries/smartyadsUtils/getAdUrlByRegion.js b/libraries/smartyadsUtils/getAdUrlByRegion.js index cad9055f671..8465d3e1584 100644 --- a/libraries/smartyadsUtils/getAdUrlByRegion.js +++ b/libraries/smartyadsUtils/getAdUrlByRegion.js @@ -1,3 +1,5 @@ +import { getTimeZone } from '../timezone/timezone.js'; + 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', @@ -10,21 +12,16 @@ export function getAdUrlByRegion(bid) { 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]; + const region = getTimeZone().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']; + switch (region) { + case 'Europe': + adUrl = adUrls['EU']; + break; + case 'Asia': + adUrl = adUrls['SGP']; + break; + default: adUrl = adUrls['US_EAST']; } } diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index f310f2304d2..2c07c8ea288 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -231,8 +231,8 @@ export const getUserSyncs = (syncUrl) => (syncOptions, serverResponses, gdprCons } } - if (uspConsent && uspConsent.consentString) { - url += `&ccpa_consent=${uspConsent.consentString}`; + if (uspConsent) { + url += `&ccpa_consent=${uspConsent}`; } if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { diff --git a/libraries/timezone/timezone.js b/libraries/timezone/timezone.js new file mode 100644 index 00000000000..8c09295d70f --- /dev/null +++ b/libraries/timezone/timezone.js @@ -0,0 +1,8 @@ +import {isFingerprintingApiDisabled} from '../fingerprinting/fingerprinting.js'; + +export function getTimeZone() { + if (isFingerprintingApiDisabled('resolvedoptions')) { + return ''; + } + return Intl.DateTimeFormat().resolvedOptions().timeZone; +} diff --git a/libraries/uid2IdSystemShared/uid2IdSystem_shared.js b/libraries/uid2IdSystemShared/uid2IdSystem_shared.js index 2230fb4efb0..70a607a0f8f 100644 --- a/libraries/uid2IdSystemShared/uid2IdSystem_shared.js +++ b/libraries/uid2IdSystemShared/uid2IdSystem_shared.js @@ -771,12 +771,6 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { }).catch((e) => { logError('error refreshing token: ', e); }); } }; } - // 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) - .catch((e) => { logError('error refreshing token in background: ', e); }); - } const tokens = { originalToken: suppliedToken ?? storedTokens?.originalToken, latestToken: newestAvailableToken, @@ -785,6 +779,23 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { tokens.originalIdentity = storedTokens?.originalIdentity; } storageManager.storeValue(tokens); + + // If should refresh (but don't need to), refresh in the background. + // Return both immediate id and callback so idObj gets updated when refresh completes. + if (Date.now() > newestAvailableToken.refresh_from) { + logInfo(`Refreshing token in background with low priority.`); + const refreshPromise = refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, logInfo, _logWarn); + return { + id: tokens, + callback: (cb) => { + refreshPromise.then((refreshedTokens) => { + logInfo('Background token refresh completed, updating ID.', refreshedTokens); + cb(refreshedTokens); + }).catch((e) => { logError('error refreshing token in background: ', e); }); + } + }; + } + return { id: tokens }; } diff --git a/libraries/webdriver/webdriver.js b/libraries/webdriver/webdriver.js index 957fea62ed8..8e10e4d0374 100644 --- a/libraries/webdriver/webdriver.js +++ b/libraries/webdriver/webdriver.js @@ -1,9 +1,49 @@ -import {canAccessWindowTop, internal as utilsInternals} from '../../src/utils.js'; +import {isFingerprintingApiDisabled} from '../fingerprinting/fingerprinting.js'; +import {getFallbackWindow} from '../../src/utils.js'; /** * Warning: accessing navigator.webdriver may impact fingerprinting scores when this API is included in the built script. + * @param {Window} [win] Window to check (defaults to top or self) + * @returns {boolean} */ -export function isWebdriverEnabled() { - const win = canAccessWindowTop() ? utilsInternals.getWindowTop() : utilsInternals.getWindowSelf(); - return win.navigator?.webdriver === true; +export function isWebdriverEnabled(win) { + if (isFingerprintingApiDisabled('webdriver')) { + return false; + } + return getFallbackWindow(win).navigator?.webdriver === true; +} + +/** + * Detects Selenium/WebDriver via document/window properties (e.g. __webdriver_script_fn, attributes). + * @param {Window} [win] Window to check + * @param {Document} [doc] Document to check (defaults to win.document) + * @returns {boolean} + */ +export function isSeleniumDetected(win, doc) { + if (isFingerprintingApiDisabled('webdriver')) { + return false; + } + const _win = win || (typeof window !== 'undefined' ? window : undefined); + const _doc = doc || (_win?.document); + if (!_win || !_doc) return false; + const checks = [ + 'webdriver' in _win, + '_Selenium_IDE_Recorder' in _win, + 'callSelenium' in _win, + '_selenium' in _win, + '__webdriver_script_fn' in _doc, + '__driver_evaluate' in _doc, + '__webdriver_evaluate' in _doc, + '__selenium_evaluate' in _doc, + '__fxdriver_evaluate' in _doc, + '__driver_unwrapped' in _doc, + '__webdriver_unwrapped' in _doc, + '__selenium_unwrapped' in _doc, + '__fxdriver_unwrapped' in _doc, + '__webdriver_script_func' in _doc, + _doc.documentElement?.getAttribute('selenium') !== null, + _doc.documentElement?.getAttribute('webdriver') !== null, + _doc.documentElement?.getAttribute('driver') !== null + ]; + return checks.some(Boolean); } diff --git a/metadata/core.json b/metadata/core.json index 1e43a89e586..faacd3ceed4 100644 --- a/metadata/core.json +++ b/metadata/core.json @@ -11,6 +11,12 @@ "moduleName": "prebid-core", "disclosureURL": "local://prebid/probes.json" }, + { + "componentType": "prebid", + "componentName": "storage", + "moduleName": "prebid-core", + "disclosureURL": "local://prebid/probes.json" + }, { "componentType": "prebid", "componentName": "debugging", diff --git a/metadata/disclosures/prebid/probes.json b/metadata/disclosures/prebid/probes.json index c371cef1d4e..16cc5dec160 100644 --- a/metadata/disclosures/prebid/probes.json +++ b/metadata/disclosures/prebid/probes.json @@ -22,7 +22,7 @@ "domains": [ { "domain": "*", - "use": "Temporary 'probing' cookies are written (and deleted) to determine the top-level domain; likewise, probes are temporarily written to local and sessionStorage to determine their availability" + "use": "Temporary 'probing' cookies are written (and deleted) to determine the top-level domain and availability of cookies; likewise, probes are temporarily written to local and sessionStorage to determine their availability" } ] } diff --git a/metadata/modules.json b/metadata/modules.json index a08bfd3b4ac..60c9f672a99 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -43,6 +43,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "aceex", + "aliasOf": null, + "gvlid": 1387, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "acuityads", @@ -106,6 +113,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "adcluster", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "addefend", @@ -512,6 +526,20 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "appmonsta", + "aliasOf": "adkernel", + "gvlid": 1283, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "intlscoop", + "aliasOf": "adkernel", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "admaru", @@ -561,6 +589,13 @@ "gvlid": 779, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "adrubi", + "aliasOf": "admatic", + "gvlid": 779, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "yobee", @@ -631,6 +666,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "adnimation", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "adnow", @@ -684,7 +726,7 @@ "componentType": "bidder", "componentName": "adocean", "aliasOf": null, - "gvlid": null, + "gvlid": 328, "disclosureURL": null }, { @@ -953,6 +995,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "alchemyx", + "aliasOf": "adverxo", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "adxcg", @@ -1016,6 +1065,13 @@ "gvlid": 1169, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "allegro", + "aliasOf": null, + "gvlid": 1493, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "alvads", @@ -1142,6 +1198,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "apester", + "aliasOf": null, + "gvlid": 354, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "appStockSSP", @@ -1191,13 +1254,6 @@ "gvlid": 32, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "emetriq", - "aliasOf": "appnexus", - "gvlid": 213, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "pagescience", @@ -1289,6 +1345,13 @@ "gvlid": 879, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "aps", + "aliasOf": null, + "gvlid": 793, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "apstream", @@ -1713,6 +1776,13 @@ "componentType": "bidder", "componentName": "clickio", "aliasOf": null, + "gvlid": 1500, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "clydo", + "aliasOf": null, "gvlid": null, "disclosureURL": null }, @@ -1919,6 +1989,20 @@ "gvlid": 573, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "das", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "ringieraxelspringer", + "aliasOf": "das", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "datablocks", @@ -2059,6 +2143,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "dpai", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "driftpixel", @@ -2248,6 +2339,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "floxis", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "fluct", @@ -2395,13 +2493,6 @@ "gvlid": null, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "trustx", - "aliasOf": "grid", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "growads", @@ -2444,6 +2535,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "harion", + "aliasOf": null, + "gvlid": 1406, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "holid", @@ -2549,6 +2647,13 @@ "gvlid": 910, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "insurads", + "aliasOf": null, + "gvlid": 596, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "integr8", @@ -2703,6 +2808,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "leagueM", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "lemmadigital", @@ -2745,13 +2857,6 @@ "gvlid": 1358, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "apester", - "aliasOf": "limelightDigital", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "adsyield", @@ -2817,28 +2922,35 @@ }, { "componentType": "bidder", - "componentName": "adnimation", + "componentName": "rtbdemand", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "rtbdemand", + "componentName": "altstar", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "altstar", + "componentName": "vaayaMedia", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "vaayaMedia", + "componentName": "performist", + "aliasOf": "limelightDigital", + "gvlid": null, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "oveeo", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null @@ -3109,6 +3221,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "mile", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "minutemedia", @@ -3165,6 +3284,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "mycodemedia", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "mytarget", @@ -3335,23 +3461,23 @@ }, { "componentType": "bidder", - "componentName": "revnew", + "componentName": "pubxai", "aliasOf": "nexx360", - "gvlid": 1468, + "gvlid": 1485, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "pubxai", + "componentName": "ybidder", "aliasOf": "nexx360", - "gvlid": 1485, + "gvlid": 1253, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "ybidder", + "componentName": "netads", "aliasOf": "nexx360", - "gvlid": 1253, + "gvlid": 965, "disclosureURL": null }, { @@ -3585,6 +3711,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "panxo", + "aliasOf": null, + "gvlid": 1527, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "performax", @@ -3739,6 +3872,20 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "pubstack", + "aliasOf": null, + "gvlid": 1408, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "pubstack_server", + "aliasOf": "pubstack", + "gvlid": 1408, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "pubx", @@ -3907,6 +4054,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "revantage", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "revcontent", @@ -3914,6 +4068,13 @@ "gvlid": 203, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "revnew", + "aliasOf": null, + "gvlid": 1468, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "rhythmone", @@ -3935,13 +4096,6 @@ "gvlid": 108, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "ringieraxelspringer", - "aliasOf": null, - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "rise", @@ -4271,6 +4425,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "radiantfusion", + "aliasOf": "smarthub", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "smartico", @@ -4509,6 +4670,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "teqBlazeSalesAgent", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "theadx", @@ -4572,6 +4740,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "trustx", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "ttd", @@ -4670,6 +4845,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "verben", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "viant", @@ -4908,6 +5090,13 @@ "gvlid": 25, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "yaleo", + "aliasOf": null, + "gvlid": 783, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "yandex", @@ -5289,10 +5478,16 @@ }, { "componentType": "rtd", - "componentName": "permutive", - "gvlid": 361, + "componentName": "panxo", + "gvlid": null, "disclosureURL": null }, + { + "componentType": "rtd", + "componentName": "permutive", + "gvlid": null, + "disclosureURL": "https://assets.permutive.app/tcf/tcf.json" + }, { "componentType": "rtd", "componentName": "pubmatic", @@ -5601,6 +5796,20 @@ "disclosureURL": null, "aliasOf": null }, + { + "componentType": "userId", + "componentName": "locId", + "gvlid": null, + "disclosureURL": null, + "aliasOf": null + }, + { + "componentType": "userId", + "componentName": "locid", + "gvlid": null, + "disclosureURL": null, + "aliasOf": "locId" + }, { "componentType": "userId", "componentName": "lockrAIMId", @@ -5695,8 +5904,8 @@ { "componentType": "userId", "componentName": "permutiveIdentityManagerId", - "gvlid": 361, - "disclosureURL": null, + "gvlid": null, + "disclosureURL": "https://assets.permutive.app/tcf/tcf.json", "aliasOf": null }, { diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index d532327e06e..79ecf25a705 100644 --- a/metadata/modules/33acrossBidAdapter.json +++ b/metadata/modules/33acrossBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2025-11-19T20:51:08.096Z", + "timestamp": "2026-03-02T14:44:46.319Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 50f8e7b1997..3adce4bba03 100644 --- a/metadata/modules/33acrossIdSystem.json +++ b/metadata/modules/33acrossIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2025-11-19T20:51:08.188Z", + "timestamp": "2026-03-02T14:44:46.422Z", "disclosures": [] } }, diff --git a/metadata/modules/aceexBidAdapter.json b/metadata/modules/aceexBidAdapter.json new file mode 100644 index 00000000000..f443e609ec4 --- /dev/null +++ b/metadata/modules/aceexBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://aceex.io/tcf.json": { + "timestamp": "2026-03-02T14:44:46.425Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "aceex", + "aliasOf": null, + "gvlid": 1387, + "disclosureURL": "https://aceex.io/tcf.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index 39f13157d79..01786a901ea 100644 --- a/metadata/modules/acuityadsBidAdapter.json +++ b/metadata/modules/acuityadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.acuityads.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:08.191Z", + "timestamp": "2026-03-02T14:44:46.471Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index 4ef5ccfc5ae..728761cdcc3 100644 --- a/metadata/modules/adagioBidAdapter.json +++ b/metadata/modules/adagioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:08.247Z", + "timestamp": "2026-03-02T14:44:46.503Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index 5d130b541fb..01aba8c973b 100644 --- a/metadata/modules/adagioRtdProvider.json +++ b/metadata/modules/adagioRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:08.295Z", + "timestamp": "2026-03-02T14:44:46.575Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index ff4d88380d3..c1607d5b2ea 100644 --- a/metadata/modules/adbroBidAdapter.json +++ b/metadata/modules/adbroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tag.adbro.me/privacy/devicestorage.json": { - "timestamp": "2025-11-19T20:51:08.295Z", + "timestamp": "2026-03-02T14:44:46.575Z", "disclosures": [] } }, diff --git a/metadata/modules/adclusterBidAdapter.json b/metadata/modules/adclusterBidAdapter.json new file mode 100644 index 00000000000..72d44231bb6 --- /dev/null +++ b/metadata/modules/adclusterBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "adcluster", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index 5732298509a..1cd4c70fc97 100644 --- a/metadata/modules/addefendBidAdapter.json +++ b/metadata/modules/addefendBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.addefend.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:08.584Z", + "timestamp": "2026-03-02T14:44:46.894Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index e18260a2a5d..8e01eb95878 100644 --- a/metadata/modules/adfBidAdapter.json +++ b/metadata/modules/adfBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://site.adform.com/assets/devicestorage.json": { - "timestamp": "2025-11-19T20:51:09.232Z", + "timestamp": "2026-03-02T14:45:00.675Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 00a4a50710f..bec524db96d 100644 --- a/metadata/modules/adfusionBidAdapter.json +++ b/metadata/modules/adfusionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spicyrtb.com/static/iab-disclosure.json": { - "timestamp": "2025-11-19T20:51:09.232Z", + "timestamp": "2026-03-02T14:45:00.676Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index 8b35bf5d541..341d5b1c818 100644 --- a/metadata/modules/adheseBidAdapter.json +++ b/metadata/modules/adheseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adhese.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:09.608Z", + "timestamp": "2026-03-02T14:45:01.041Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index a106bd54e64..54ee67e7037 100644 --- a/metadata/modules/adipoloBidAdapter.json +++ b/metadata/modules/adipoloBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adipolo.com/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:09.872Z", + "timestamp": "2026-03-02T14:45:01.314Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index 2e3a6eba0b5..0516f01d293 100644 --- a/metadata/modules/adkernelAdnBidAdapter.json +++ b/metadata/modules/adkernelAdnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:10.019Z", + "timestamp": "2026-03-02T14:45:01.470Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index e073819755d..8b11e6d7d24 100644 --- a/metadata/modules/adkernelBidAdapter.json +++ b/metadata/modules/adkernelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:10.063Z", + "timestamp": "2026-03-02T14:45:01.554Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,15 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:10.063Z", + "timestamp": "2026-03-02T14:45:01.554Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2025-11-19T20:51:10.114Z", + "timestamp": "2026-03-02T14:45:01.604Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:10.823Z", + "timestamp": "2026-03-02T14:45:02.320Z", + "disclosures": [] + }, + "https://appmonsta.ai/DeviceStorageDisclosure.json": { + "timestamp": "2026-03-02T14:45:02.337Z", "disclosures": [] } }, @@ -337,6 +341,20 @@ "aliasOf": "adkernel", "gvlid": null, "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "appmonsta", + "aliasOf": "adkernel", + "gvlid": 1283, + "disclosureURL": "https://appmonsta.ai/DeviceStorageDisclosure.json" + }, + { + "componentType": "bidder", + "componentName": "intlscoop", + "aliasOf": "adkernel", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index 49230562aff..ded364cbc31 100644 --- a/metadata/modules/admaticBidAdapter.json +++ b/metadata/modules/admaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.admatic.de/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2025-11-19T20:51:11.408Z", + "timestamp": "2026-03-02T14:45:02.895Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:10.948Z", + "timestamp": "2026-03-02T14:45:02.895Z", "disclosures": [ { "identifier": "adt_pbjs", @@ -65,6 +65,13 @@ "gvlid": 779, "disclosureURL": "https://adtarget.com.tr/.well-known/deviceStorage.json" }, + { + "componentType": "bidder", + "componentName": "adrubi", + "aliasOf": "admatic", + "gvlid": 779, + "disclosureURL": "https://adtarget.com.tr/.well-known/deviceStorage.json" + }, { "componentType": "bidder", "componentName": "yobee", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index 37962a173e4..8f6d6f99397 100644 --- a/metadata/modules/admixerBidAdapter.json +++ b/metadata/modules/admixerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2025-11-19T20:51:11.409Z", + "timestamp": "2026-03-02T14:45:02.896Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index cedbe489a6d..f8bd80f3bca 100644 --- a/metadata/modules/admixerIdSystem.json +++ b/metadata/modules/admixerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2025-11-19T20:51:11.787Z", + "timestamp": "2026-03-02T14:45:03.276Z", "disclosures": [] } }, diff --git a/metadata/modules/adnimationBidAdapter.json b/metadata/modules/adnimationBidAdapter.json new file mode 100644 index 00000000000..7bb7fce194e --- /dev/null +++ b/metadata/modules/adnimationBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "adnimation", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index 21d4b63fd47..cb5aa129b20 100644 --- a/metadata/modules/adnowBidAdapter.json +++ b/metadata/modules/adnowBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adnow.com/vdsod.json": { - "timestamp": "2025-11-19T20:51:11.787Z", + "timestamp": "2026-03-02T14:45:03.276Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index 8137782114a..c2a643ef408 100644 --- a/metadata/modules/adnuntiusBidAdapter.json +++ b/metadata/modules/adnuntiusBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:12.039Z", + "timestamp": "2026-03-02T14:45:16.546Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index 7cbe053afbd..9c3af875f20 100644 --- a/metadata/modules/adnuntiusRtdProvider.json +++ b/metadata/modules/adnuntiusRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:12.355Z", + "timestamp": "2026-03-02T14:45:16.877Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index 9743e913146..c97b093663c 100644 --- a/metadata/modules/adoceanBidAdapter.json +++ b/metadata/modules/adoceanBidAdapter.json @@ -1,13 +1,280 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, + "disclosures": { + "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { + "timestamp": "2026-03-02T14:45:16.878Z", + "disclosures": [ + { + "identifier": "__gsyncs_gdpr", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "__gsync_gdpr", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": true, + "purposes": [ + 1 + ] + }, + { + "identifier": "__gsync_s_gdpr", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": true, + "purposes": [ + 1 + ] + }, + { + "identifier": "__gfp_cap", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_s_cap", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_cache", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_s_cache", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_64b", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_s_64b", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfps_64b", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "gemius_ruid", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": null, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_dnt", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": true, + "purposes": [ + 1 + ], + "optOut": true + }, + { + "identifier": "__gfp_s_dnt", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": true, + "purposes": [ + 1 + ], + "optOut": true + }, + { + "identifier": "__gfp_ruid", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_s_ruid", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_ruid_pub", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_s_ruid_pub", + "type": "cookie", + "maxAgeSeconds": 34128000, + "cookieRefresh": true, + "purposes": [ + 1, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "__gfp_gdpr", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "__gfp_s_gdpr", + "type": "cookie", + "maxAgeSeconds": 157680000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "ao-fpgad", + "type": "cookie", + "maxAgeSeconds": 33696000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 7, + 8, + 9, + 10 + ] + }, + { + "identifier": "AO-OPT-OUT", + "type": "cookie", + "maxAgeSeconds": 155520000, + "cookieRefresh": false, + "purposes": [ + 1 + ], + "optOut": true + }, + { + "identifier": "_ao_consent_data", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": null, + "purposes": [] + }, + { + "identifier": "_ao_chints", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": null, + "purposes": [] + } + ] + } + }, "components": [ { "componentType": "bidder", "componentName": "adocean", "aliasOf": null, - "gvlid": null, - "disclosureURL": null + "gvlid": 328, + "disclosureURL": "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json" } ] } \ No newline at end of file diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index 4583bae817b..9a38b9d66e3 100644 --- a/metadata/modules/adotBidAdapter.json +++ b/metadata/modules/adotBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.adotmob.com/tcf/tcf.json": { - "timestamp": "2025-11-19T20:51:12.355Z", + "timestamp": "2026-03-02T14:45:17.430Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index 7e729f814b7..7022a062e25 100644 --- a/metadata/modules/adponeBidAdapter.json +++ b/metadata/modules/adponeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.adpone.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:12.400Z", + "timestamp": "2026-03-02T14:45:17.575Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index 567fc0e4041..6ee1ee7d899 100644 --- a/metadata/modules/adqueryBidAdapter.json +++ b/metadata/modules/adqueryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2025-11-19T20:51:12.421Z", + "timestamp": "2026-03-02T14:45:17.832Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index d8b870afe6b..d97e79287da 100644 --- a/metadata/modules/adqueryIdSystem.json +++ b/metadata/modules/adqueryIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2025-11-19T20:51:12.756Z", + "timestamp": "2026-03-02T14:45:18.165Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 2e3a762ddcd..1683ae8d010 100644 --- a/metadata/modules/adrinoBidAdapter.json +++ b/metadata/modules/adrinoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.adrino.cloud/iab/device-storage.json": { - "timestamp": "2025-11-19T20:51:12.757Z", + "timestamp": "2026-03-02T14:45:18.166Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index fd47c760862..c0bcc3c0438 100644 --- a/metadata/modules/ads_interactiveBidAdapter.json +++ b/metadata/modules/ads_interactiveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adsinteractive.com/vendor.json": { - "timestamp": "2025-11-19T20:51:12.854Z", + "timestamp": "2026-03-02T14:45:18.257Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index 4b559c80cdc..643548e6a49 100644 --- a/metadata/modules/adtargetBidAdapter.json +++ b/metadata/modules/adtargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:13.161Z", + "timestamp": "2026-03-02T14:45:18.549Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index b3fb4a5db30..68c149946c6 100644 --- a/metadata/modules/adtelligentBidAdapter.json +++ b/metadata/modules/adtelligentBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:13.161Z", + "timestamp": "2026-03-02T14:45:18.549Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { - "timestamp": "2025-11-19T20:51:13.180Z", + "timestamp": "2026-03-02T14:45:18.568Z", "disclosures": [ { "identifier": "waterFallCacheAnsKey_*", @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:13.354Z", + "timestamp": "2026-03-02T14:45:31.321Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index e62d3831e88..63773fb0465 100644 --- a/metadata/modules/adtelligentIdSystem.json +++ b/metadata/modules/adtelligentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:13.523Z", + "timestamp": "2026-03-02T14:45:31.395Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index ac02403710e..63a0f554c7f 100644 --- a/metadata/modules/aduptechBidAdapter.json +++ b/metadata/modules/aduptechBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.d.adup-tech.com/gdpr/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:13.524Z", + "timestamp": "2026-03-02T14:45:31.395Z", "disclosures": [] } }, diff --git a/metadata/modules/adverxoBidAdapter.json b/metadata/modules/adverxoBidAdapter.json index cece61bdaf1..e3cec5f59de 100644 --- a/metadata/modules/adverxoBidAdapter.json +++ b/metadata/modules/adverxoBidAdapter.json @@ -29,6 +29,13 @@ "aliasOf": "adverxo", "gvlid": null, "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "alchemyx", + "aliasOf": "adverxo", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index b4066e9b8ef..80d9bc14b7e 100644 --- a/metadata/modules/adyoulikeBidAdapter.json +++ b/metadata/modules/adyoulikeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adyoulike.com/deviceStorageDisclosureURL.json": { - "timestamp": "2025-11-19T20:51:13.552Z", + "timestamp": "2026-03-02T14:45:31.420Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index 46c58dddb5e..e127fd2d00b 100644 --- a/metadata/modules/airgridRtdProvider.json +++ b/metadata/modules/airgridRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.wearemiq.com/privacy-and-compliance/devicestoragedisclosures.json": { - "timestamp": "2025-11-19T20:51:14.020Z", + "timestamp": "2026-03-02T14:45:31.871Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index ad298eddabb..a9638ab0a47 100644 --- a/metadata/modules/alkimiBidAdapter.json +++ b/metadata/modules/alkimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d1xjh92lb8fey3.cloudfront.net/tcf/alkimi_exchange_tcf.json": { - "timestamp": "2025-11-19T20:51:14.058Z", + "timestamp": "2026-03-02T14:45:31.902Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json new file mode 100644 index 00000000000..0d631041437 --- /dev/null +++ b/metadata/modules/allegroBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://assets.allegrostatic.com/dsp-tcf-external/device-storage.json": { + "timestamp": "2026-03-02T14:45:32.202Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "allegro", + "aliasOf": null, + "gvlid": 1493, + "disclosureURL": "https://assets.allegrostatic.com/dsp-tcf-external/device-storage.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index 0494a5ea70b..8a4900eb3fb 100644 --- a/metadata/modules/amxBidAdapter.json +++ b/metadata/modules/amxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2025-11-19T20:51:14.354Z", + "timestamp": "2026-03-02T14:45:32.667Z", "disclosures": [] } }, diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index 77076a0455c..a204078442a 100644 --- a/metadata/modules/amxIdSystem.json +++ b/metadata/modules/amxIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2025-11-19T20:51:14.408Z", + "timestamp": "2026-03-02T14:45:32.757Z", "disclosures": [] } }, diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index fdc85537934..0bc1a7c7752 100644 --- a/metadata/modules/aniviewBidAdapter.json +++ b/metadata/modules/aniviewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.aniview.com/gdpr/gdpr.json": { - "timestamp": "2025-11-19T20:51:14.408Z", + "timestamp": "2026-03-02T14:45:32.758Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 827805b370f..7593b8bcb11 100644 --- a/metadata/modules/anonymisedRtdProvider.json +++ b/metadata/modules/anonymisedRtdProvider.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://static.anonymised.io/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:14.519Z", + "https://cdn1.anonymised.io/deviceStorage.json": { + "timestamp": "2026-03-02T14:45:32.828Z", "disclosures": [ { "identifier": "oidc.user*", @@ -67,7 +67,7 @@ "componentType": "rtd", "componentName": "anonymised", "gvlid": 1116, - "disclosureURL": "https://static.anonymised.io/deviceStorage.json" + "disclosureURL": "https://cdn1.anonymised.io/deviceStorage.json" } ] } \ No newline at end of file diff --git a/metadata/modules/apesterBidAdapter.json b/metadata/modules/apesterBidAdapter.json new file mode 100644 index 00000000000..372e0ba6a78 --- /dev/null +++ b/metadata/modules/apesterBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://apester.com/deviceStorage.json": { + "timestamp": "2026-03-02T14:45:33.047Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "apester", + "aliasOf": null, + "gvlid": 354, + "disclosureURL": "https://apester.com/deviceStorage.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index 6dd538cd5bf..8d113568028 100644 --- a/metadata/modules/appStockSSPBidAdapter.json +++ b/metadata/modules/appStockSSPBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://app-stock.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:14.636Z", + "timestamp": "2026-03-02T14:45:33.167Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index 831bda55c41..bc805982843 100644 --- a/metadata/modules/appierBidAdapter.json +++ b/metadata/modules/appierBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.appier.com/deviceStorage2025.json": { - "timestamp": "2025-11-19T20:51:14.690Z", + "timestamp": "2026-03-02T14:45:33.203Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index 99a201651de..59027a399d7 100644 --- a/metadata/modules/appnexusBidAdapter.json +++ b/metadata/modules/appnexusBidAdapter.json @@ -2,23 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2025-11-19T20:51:15.356Z", - "disclosures": [] - }, - "https://tcf.emetriq.de/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:14.828Z", + "timestamp": "2026-03-02T14:45:33.960Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:14.849Z", + "timestamp": "2026-03-02T14:45:33.279Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:14.865Z", + "timestamp": "2026-03-02T14:45:33.420Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2025-11-19T20:51:15.356Z", + "timestamp": "2026-03-02T14:45:33.960Z", "disclosures": [] } }, @@ -37,13 +33,6 @@ "gvlid": 32, "disclosureURL": "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json" }, - { - "componentType": "bidder", - "componentName": "emetriq", - "aliasOf": "appnexus", - "gvlid": 213, - "disclosureURL": "https://tcf.emetriq.de/deviceStorageDisclosure.json" - }, { "componentType": "bidder", "componentName": "pagescience", diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index c0935899043..15019d11110 100644 --- a/metadata/modules/appushBidAdapter.json +++ b/metadata/modules/appushBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.thebiding.com/disclosures.json": { - "timestamp": "2025-11-19T20:51:15.414Z", + "timestamp": "2026-03-02T14:45:33.989Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json new file mode 100644 index 00000000000..7132ee483e9 --- /dev/null +++ b/metadata/modules/apsBidAdapter.json @@ -0,0 +1,49 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://m.media-amazon.com/images/G/01/adprefs/deviceStorageDisclosure.json": { + "timestamp": "2026-03-02T14:45:34.055Z", + "disclosures": [ + { + "identifier": "vendor-id", + "type": "cookie", + "maxAgeSeconds": 2592000, + "cookieRefresh": true, + "purposes": [ + 1, + 7 + ] + }, + { + "identifier": "amzn-token", + "type": "cookie", + "maxAgeSeconds": 604800, + "cookieRefresh": true, + "purposes": [ + 1 + ] + }, + { + "identifier": "*sessionMarker/marker", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1, + 2, + 7 + ] + } + ] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "aps", + "aliasOf": null, + "gvlid": 793, + "disclosureURL": "https://m.media-amazon.com/images/G/01/adprefs/deviceStorageDisclosure.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index 86ba9c1dc17..b7a2fb2671d 100644 --- a/metadata/modules/apstreamBidAdapter.json +++ b/metadata/modules/apstreamBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sak.userreport.com/tcf.json": { - "timestamp": "2025-11-19T20:51:15.528Z", + "timestamp": "2026-03-02T14:45:34.071Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index d308af1c05b..ad7d2d71fc5 100644 --- a/metadata/modules/audiencerunBidAdapter.json +++ b/metadata/modules/audiencerunBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.audiencerun.com/tcf.json": { - "timestamp": "2025-11-19T20:51:15.551Z", + "timestamp": "2026-03-02T14:45:34.115Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index 119c83c1d1a..33ade7a617c 100644 --- a/metadata/modules/axisBidAdapter.json +++ b/metadata/modules/axisBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://axis-marketplace.com/tcf.json": { - "timestamp": "2025-11-19T20:51:15.615Z", + "timestamp": "2026-03-02T14:45:34.167Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 9dacb4aca40..198266cb6ac 100644 --- a/metadata/modules/azerionedgeRtdProvider.json +++ b/metadata/modules/azerionedgeRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2025-11-19T20:51:15.658Z", + "timestamp": "2026-03-02T14:45:34.210Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index 63d6f32a7c8..b7f18cad4eb 100644 --- a/metadata/modules/beachfrontBidAdapter.json +++ b/metadata/modules/beachfrontBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2025-11-19T20:51:15.684Z", + "timestamp": "2026-03-02T14:45:34.236Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 3fd5c0a4a8d..84b4ecc7f33 100644 --- a/metadata/modules/beopBidAdapter.json +++ b/metadata/modules/beopBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://beop.io/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:16.048Z", + "timestamp": "2026-03-02T14:45:34.257Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index 36747536871..b48dadd84d0 100644 --- a/metadata/modules/betweenBidAdapter.json +++ b/metadata/modules/betweenBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.betweenx.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:16.175Z", + "timestamp": "2026-03-02T14:45:34.379Z", "disclosures": [] } }, diff --git a/metadata/modules/bidfuseBidAdapter.json b/metadata/modules/bidfuseBidAdapter.json index 0f1ca2fa3fa..cb5dc975164 100644 --- a/metadata/modules/bidfuseBidAdapter.json +++ b/metadata/modules/bidfuseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bidfuse.com/disclosure.json": { - "timestamp": "2025-11-19T20:51:16.229Z", + "timestamp": "2026-03-02T14:45:34.436Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index 465dd2cbdfe..320c69b6e66 100644 --- a/metadata/modules/bidmaticBidAdapter.json +++ b/metadata/modules/bidmaticBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bidmatic.io/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:16.409Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:34.611Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index 371cf75c451..987e79d876e 100644 --- a/metadata/modules/bidtheatreBidAdapter.json +++ b/metadata/modules/bidtheatreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.bidtheatre.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:16.457Z", + "timestamp": "2026-03-02T14:45:34.658Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index e04e63b647b..9ae146cb5de 100644 --- a/metadata/modules/bliinkBidAdapter.json +++ b/metadata/modules/bliinkBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bliink.io/disclosures.json": { - "timestamp": "2025-11-19T20:51:16.779Z", + "timestamp": "2026-03-02T14:45:34.957Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index 54fd5fb8aaf..b3c53c81f31 100644 --- a/metadata/modules/blockthroughBidAdapter.json +++ b/metadata/modules/blockthroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://blockthrough.com/tcf_disclosures.json": { - "timestamp": "2025-11-10T11:41:19.544Z", + "timestamp": "2026-03-02T14:45:35.252Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index a0de664de61..fdefc12b8e7 100644 --- a/metadata/modules/blueBidAdapter.json +++ b/metadata/modules/blueBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://getblue.io/iab/iab.json": { - "timestamp": "2025-11-19T20:51:17.219Z", + "timestamp": "2026-03-02T14:45:35.438Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index c3f5aae4563..59d3a1914dc 100644 --- a/metadata/modules/bmsBidAdapter.json +++ b/metadata/modules/bmsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bluems.com/iab.json": { - "timestamp": "2025-11-19T20:51:17.572Z", + "timestamp": "2026-03-02T14:45:35.784Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index 75dd1c781be..e8f694fd2b9 100644 --- a/metadata/modules/boldwinBidAdapter.json +++ b/metadata/modules/boldwinBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://magav.videowalldirect.com/iab/videowalldirectiab.json": { - "timestamp": "2025-11-19T20:51:17.595Z", + "timestamp": "2026-03-02T14:45:35.801Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 597e7796973..4475c15ed3a 100644 --- a/metadata/modules/bridBidAdapter.json +++ b/metadata/modules/bridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2025-11-19T20:51:17.700Z", + "timestamp": "2026-03-02T14:45:35.828Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index d81cca591e1..ddc532d4e32 100644 --- a/metadata/modules/browsiBidAdapter.json +++ b/metadata/modules/browsiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.browsiprod.com/ads/tcf.json": { - "timestamp": "2025-11-19T20:51:17.840Z", + "timestamp": "2026-03-02T14:45:35.966Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index 8d71736c557..571e8dac8d9 100644 --- a/metadata/modules/bucksenseBidAdapter.json +++ b/metadata/modules/bucksenseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://j.bksnimages.com/iab/devsto02.json": { - "timestamp": "2025-11-19T20:51:17.857Z", + "timestamp": "2026-03-02T14:45:36.025Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index 5c565ba1aa8..06cedda2cbb 100644 --- a/metadata/modules/carodaBidAdapter.json +++ b/metadata/modules/carodaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn2.caroda.io/tcfvds/2022-05-17/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:17.919Z", + "timestamp": "2026-03-02T14:45:36.084Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index 921493ce0fb..1b8ecd5185e 100644 --- a/metadata/modules/categoryTranslation.json +++ b/metadata/modules/categoryTranslation.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json": { - "timestamp": "2025-11-19T20:51:08.094Z", + "timestamp": "2026-03-02T14:44:46.317Z", "disclosures": [ { "identifier": "iabToFwMappingkey", diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 307b7549665..414cf9f61c3 100644 --- a/metadata/modules/ceeIdSystem.json +++ b/metadata/modules/ceeIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:18.230Z", + "timestamp": "2026-03-02T14:45:36.379Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index 16accf9cae2..0374f04c2e3 100644 --- a/metadata/modules/chromeAiRtdProvider.json +++ b/metadata/modules/chromeAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/chromeAiRtdProvider.json": { - "timestamp": "2025-11-19T20:51:18.581Z", + "timestamp": "2026-03-02T14:45:36.729Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index de263bb0585..ec57e47521f 100644 --- a/metadata/modules/clickioBidAdapter.json +++ b/metadata/modules/clickioBidAdapter.json @@ -1,13 +1,18 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, + "disclosures": { + "https://o.clickiocdn.com/tcf_storage_info.json": { + "timestamp": "2026-03-02T14:45:36.730Z", + "disclosures": [] + } + }, "components": [ { "componentType": "bidder", "componentName": "clickio", "aliasOf": null, - "gvlid": null, - "disclosureURL": null + "gvlid": 1500, + "disclosureURL": "https://o.clickiocdn.com/tcf_storage_info.json" } ] } \ No newline at end of file diff --git a/metadata/modules/clydoBidAdapter.json b/metadata/modules/clydoBidAdapter.json new file mode 100644 index 00000000000..9d4cca50254 --- /dev/null +++ b/metadata/modules/clydoBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "clydo", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index 925c8facc3f..145961029d1 100644 --- a/metadata/modules/compassBidAdapter.json +++ b/metadata/modules/compassBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2025-11-19T20:51:18.583Z", + "timestamp": "2026-03-02T14:45:37.148Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index edbf991fc56..41d01c918e0 100644 --- a/metadata/modules/conceptxBidAdapter.json +++ b/metadata/modules/conceptxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cncptx.com/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:18.599Z", + "timestamp": "2026-03-02T14:45:37.166Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index 9a4634d837c..a1b7a772220 100644 --- a/metadata/modules/connatixBidAdapter.json +++ b/metadata/modules/connatixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://connatix.com/iab-tcf-disclosure.json": { - "timestamp": "2025-11-19T20:51:18.626Z", + "timestamp": "2026-03-02T14:45:37.190Z", "disclosures": [ { "identifier": "cnx_userId", @@ -12,22 +12,10 @@ "purposes": [ 1, 2, + 3, 4, 7, - 8 - ] - }, - { - "identifier": "cnx_player_reload", - "type": "cookie", - "maxAgeSeconds": 60, - "cookieRefresh": false, - "purposes": [ - 1, - 2, - 4, - 7, - 8 + 10 ] } ] diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index 92943284539..0cd76902e9b 100644 --- a/metadata/modules/connectIdSystem.json +++ b/metadata/modules/connectIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:18.722Z", + "timestamp": "2026-03-02T14:45:37.270Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index 474b3ab83eb..96bc8e141d6 100644 --- a/metadata/modules/connectadBidAdapter.json +++ b/metadata/modules/connectadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.connectad.io/tcf_storage_info.json": { - "timestamp": "2025-11-19T20:51:18.745Z", + "timestamp": "2026-03-02T14:45:37.290Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index d53b22757e2..7b5a61930c9 100644 --- a/metadata/modules/contentexchangeBidAdapter.json +++ b/metadata/modules/contentexchangeBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://hb.contentexchange.me/template/device_storage.json": { - "timestamp": "2025-11-10T11:41:21.385Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:37.325Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index 5cab777a157..5999b1b9c9a 100644 --- a/metadata/modules/conversantBidAdapter.json +++ b/metadata/modules/conversantBidAdapter.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:18.822Z", + "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { + "timestamp": "2026-03-02T14:45:37.366Z", "disclosures": [ { "identifier": "dtm_status", @@ -583,7 +583,7 @@ "componentName": "conversant", "aliasOf": null, "gvlid": 24, - "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json" + "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json" }, { "componentType": "bidder", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 164f7677af4..1bc771d2901 100644 --- a/metadata/modules/copper6sspBidAdapter.json +++ b/metadata/modules/copper6sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.copper6.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:18.845Z", + "timestamp": "2026-03-02T14:45:37.458Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index 9b49d8dfb84..255976fa486 100644 --- a/metadata/modules/cpmstarBidAdapter.json +++ b/metadata/modules/cpmstarBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2025-11-19T20:51:18.971Z", + "timestamp": "2026-03-02T14:45:37.500Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index c405acba13c..fb94d78e961 100644 --- a/metadata/modules/criteoBidAdapter.json +++ b/metadata/modules/criteoBidAdapter.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://privacy.criteo.com/iab-europe/tcfv2/disclosure": { - "timestamp": "2025-11-19T20:51:19.021Z", + "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { + "timestamp": "2026-03-02T14:45:37.599Z", "disclosures": [ { "identifier": "criteo_fast_bid", @@ -69,7 +69,7 @@ "componentName": "criteo", "aliasOf": null, "gvlid": 91, - "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure" + "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json" } ] } \ No newline at end of file diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index cf3766536e9..d0e56b65ba1 100644 --- a/metadata/modules/criteoIdSystem.json +++ b/metadata/modules/criteoIdSystem.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://privacy.criteo.com/iab-europe/tcfv2/disclosure": { - "timestamp": "2025-11-19T20:51:19.041Z", + "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { + "timestamp": "2026-03-02T14:45:37.624Z", "disclosures": [ { "identifier": "criteo_fast_bid", @@ -68,7 +68,7 @@ "componentType": "userId", "componentName": "criteo", "gvlid": 91, - "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure", + "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json", "aliasOf": null } ] diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index fb9e068e4e3..265357a9c7f 100644 --- a/metadata/modules/cwireBidAdapter.json +++ b/metadata/modules/cwireBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.cwi.re/artifacts/iab/iab.json": { - "timestamp": "2025-11-19T20:51:19.041Z", + "timestamp": "2026-03-02T14:45:37.625Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index e4b953bfb59..7a945949fe0 100644 --- a/metadata/modules/czechAdIdSystem.json +++ b/metadata/modules/czechAdIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cpex.cz/storagedisclosure.json": { - "timestamp": "2025-11-19T20:51:19.388Z", + "timestamp": "2026-03-02T14:45:37.988Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index c48795f0a1e..6779bc9b8a9 100644 --- a/metadata/modules/dailymotionBidAdapter.json +++ b/metadata/modules/dailymotionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://statics.dmcdn.net/a/vds.json": { - "timestamp": "2025-11-19T20:51:19.691Z", + "timestamp": "2026-03-02T14:45:38.394Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/dasBidAdapter.json b/metadata/modules/dasBidAdapter.json new file mode 100644 index 00000000000..1ca1c72523f --- /dev/null +++ b/metadata/modules/dasBidAdapter.json @@ -0,0 +1,20 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "das", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "ringieraxelspringer", + "aliasOf": "das", + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index 00eed3703dd..d8035174738 100644 --- a/metadata/modules/debugging.json +++ b/metadata/modules/debugging.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2025-11-19T20:51:08.093Z", + "timestamp": "2026-03-02T14:44:46.316Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index 8b38c30277a..f9698c4095a 100644 --- a/metadata/modules/deepintentBidAdapter.json +++ b/metadata/modules/deepintentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.deepintent.com/iabeurope_vendor_disclosures.json": { - "timestamp": "2025-11-19T20:51:19.711Z", + "timestamp": "2026-02-23T16:45:55.384Z", "disclosures": [] } }, diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index 95a5a4ccfc6..cc0c299d4f7 100644 --- a/metadata/modules/defineMediaBidAdapter.json +++ b/metadata/modules/defineMediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://definemedia.de/tcf/deviceStorageDisclosureURL.json": { - "timestamp": "2025-11-19T20:51:20.094Z", + "timestamp": "2026-03-02T14:45:38.686Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index 02545785549..ca2f76b4695 100644 --- a/metadata/modules/deltaprojectsBidAdapter.json +++ b/metadata/modules/deltaprojectsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.de17a.com/policy/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:20.507Z", + "timestamp": "2026-03-02T14:45:39.098Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index 7131f0c4f2c..1ee3130984b 100644 --- a/metadata/modules/dianomiBidAdapter.json +++ b/metadata/modules/dianomiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.dianomi.com/device_storage.json": { - "timestamp": "2025-11-19T20:51:20.947Z", + "timestamp": "2026-03-02T14:45:44.672Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index b3999a2f058..8d8e30c4102 100644 --- a/metadata/modules/digitalMatterBidAdapter.json +++ b/metadata/modules/digitalMatterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://digitalmatter.ai/disclosures.json": { - "timestamp": "2025-11-19T20:51:20.947Z", + "timestamp": "2026-03-02T14:45:44.672Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index f147261d7b4..b19604b5704 100644 --- a/metadata/modules/distroscaleBidAdapter.json +++ b/metadata/modules/distroscaleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.jsrdn.com/tcf/tcf-vendor-disclosure.json": { - "timestamp": "2025-11-19T20:51:21.331Z", + "timestamp": "2026-03-02T14:45:45.048Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index 11b3ddc6a64..676eab6e3c8 100644 --- a/metadata/modules/docereeAdManagerBidAdapter.json +++ b/metadata/modules/docereeAdManagerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:21.358Z", + "timestamp": "2026-03-02T14:45:45.334Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index d120fbe6044..14a0769efca 100644 --- a/metadata/modules/docereeBidAdapter.json +++ b/metadata/modules/docereeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:22.114Z", + "timestamp": "2026-03-02T14:45:46.086Z", "disclosures": [] } }, diff --git a/metadata/modules/dpaiBidAdapter.json b/metadata/modules/dpaiBidAdapter.json new file mode 100644 index 00000000000..901b09a3355 --- /dev/null +++ b/metadata/modules/dpaiBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "dpai", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index fa0a5901037..579ccaef7ac 100644 --- a/metadata/modules/dspxBidAdapter.json +++ b/metadata/modules/dspxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/os.json": { - "timestamp": "2025-11-19T20:51:22.115Z", + "timestamp": "2026-03-02T14:45:46.087Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index e4c50af4c11..1410a3e4917 100644 --- a/metadata/modules/e_volutionBidAdapter.json +++ b/metadata/modules/e_volutionBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://e-volution.ai/file.json": { - "timestamp": "2025-11-19T20:51:22.794Z", - "disclosures": null + "timestamp": "2026-03-02T14:45:46.760Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index b73461bb162..58cc40d3ace 100644 --- a/metadata/modules/edge226BidAdapter.json +++ b/metadata/modules/edge226BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.serveteck.com/cdn_storage/tcf/tcf.json?a=1.io": { - "timestamp": "2025-11-19T20:51:23.104Z", + "timestamp": "2026-03-02T14:45:47.084Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index 03faa868057..5b8c1e60d93 100644 --- a/metadata/modules/empowerBidAdapter.json +++ b/metadata/modules/empowerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.empower.net/vendor/vendor.json": { - "timestamp": "2025-11-19T20:51:23.177Z", + "timestamp": "2026-03-02T14:45:47.136Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index ac701e3462c..a618d1b2dd7 100644 --- a/metadata/modules/equativBidAdapter.json +++ b/metadata/modules/equativBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2025-11-19T20:51:23.210Z", + "timestamp": "2026-03-02T14:45:47.169Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index f3f5575ceeb..8908f454cef 100644 --- a/metadata/modules/eskimiBidAdapter.json +++ b/metadata/modules/eskimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://dsp-media.eskimi.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:23.853Z", + "timestamp": "2026-03-02T14:45:47.201Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index eb2ef4ed708..db71948ec37 100644 --- a/metadata/modules/etargetBidAdapter.json +++ b/metadata/modules/etargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.etarget.sk/cookies3.json": { - "timestamp": "2025-11-19T20:51:23.873Z", + "timestamp": "2026-03-02T14:45:47.226Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 430d3c77873..5d3d333813e 100644 --- a/metadata/modules/euidIdSystem.json +++ b/metadata/modules/euidIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2025-11-19T20:51:24.550Z", + "timestamp": "2026-03-02T14:45:47.806Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index b93abf1f5aa..4f78604a882 100644 --- a/metadata/modules/exadsBidAdapter.json +++ b/metadata/modules/exadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.native7.com/tcf/deviceStorage.php": { - "timestamp": "2025-11-19T20:51:24.771Z", + "timestamp": "2026-03-02T14:45:48.013Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index 6385f9ed012..898c77b0f70 100644 --- a/metadata/modules/feedadBidAdapter.json +++ b/metadata/modules/feedadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.feedad.com/tcf-device-disclosures.json": { - "timestamp": "2025-11-19T20:51:24.973Z", + "timestamp": "2026-03-02T14:45:48.194Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/floxisBidAdapter.json b/metadata/modules/floxisBidAdapter.json new file mode 100644 index 00000000000..c58d76bc57a --- /dev/null +++ b/metadata/modules/floxisBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "floxis", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index 5a38f86edb4..c950b152e00 100644 --- a/metadata/modules/fwsspBidAdapter.json +++ b/metadata/modules/fwsspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.fwmrm.net/g/devicedisclosure.json": { - "timestamp": "2025-11-19T20:51:25.329Z", + "timestamp": "2026-03-02T14:45:48.313Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index b72069e034e..05ce430c856 100644 --- a/metadata/modules/gamoshiBidAdapter.json +++ b/metadata/modules/gamoshiBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.gamoshi.com/disclosures-client-storage.json": { - "timestamp": "2025-11-19T20:51:25.921Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:48.653Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index fb4dfba197d..9aa5b49be2d 100644 --- a/metadata/modules/gemiusIdSystem.json +++ b/metadata/modules/gemiusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2025-11-19T20:51:26.097Z", + "timestamp": "2026-03-02T14:45:49.814Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", @@ -142,7 +142,8 @@ "cookieRefresh": true, "purposes": [ 1 - ] + ], + "optOut": true }, { "identifier": "__gfp_s_dnt", @@ -151,7 +152,8 @@ "cookieRefresh": true, "purposes": [ 1 - ] + ], + "optOut": true }, { "identifier": "__gfp_ruid", @@ -246,7 +248,8 @@ "cookieRefresh": false, "purposes": [ 1 - ] + ], + "optOut": true }, { "identifier": "_ao_consent_data", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 5df2c608934..3ad4e7c7df2 100644 --- a/metadata/modules/glomexBidAdapter.json +++ b/metadata/modules/glomexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:26.671Z", + "timestamp": "2026-03-02T14:45:49.816Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 1104c937c2d..61b5f9f87a3 100644 --- a/metadata/modules/goldbachBidAdapter.json +++ b/metadata/modules/goldbachBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gb-next.ch/TcfGoldbachDeviceStorage.json": { - "timestamp": "2025-11-19T20:51:26.704Z", + "timestamp": "2026-03-02T14:45:49.839Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index b3866edd805..0420f51b838 100644 --- a/metadata/modules/gridBidAdapter.json +++ b/metadata/modules/gridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.themediagrid.com/devicestorage.json": { - "timestamp": "2025-11-19T20:51:26.766Z", + "timestamp": "2026-03-02T14:45:49.864Z", "disclosures": [] } }, @@ -34,13 +34,6 @@ "aliasOf": "grid", "gvlid": null, "disclosureURL": null - }, - { - "componentType": "bidder", - "componentName": "trustx", - "aliasOf": "grid", - "gvlid": null, - "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index 5067cac4039..9131fda39bc 100644 --- a/metadata/modules/gumgumBidAdapter.json +++ b/metadata/modules/gumgumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://marketing.gumgum.com/devicestoragedisclosures.json": { - "timestamp": "2025-11-19T20:51:26.916Z", + "timestamp": "2026-03-02T14:45:49.990Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index 5930b2bfbd5..a02dd109e19 100644 --- a/metadata/modules/hadronIdSystem.json +++ b/metadata/modules/hadronIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2025-11-19T20:51:26.988Z", + "timestamp": "2026-03-02T14:45:50.048Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index 9d4a1d5dfd2..b5eb21067e8 100644 --- a/metadata/modules/hadronRtdProvider.json +++ b/metadata/modules/hadronRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2025-11-19T20:51:27.109Z", + "timestamp": "2026-03-02T14:45:50.186Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/harionBidAdapter.json b/metadata/modules/harionBidAdapter.json new file mode 100644 index 00000000000..dc71450537a --- /dev/null +++ b/metadata/modules/harionBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://markappmedia.site/vendor.json": { + "timestamp": "2026-03-02T14:45:50.186Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "harion", + "aliasOf": null, + "gvlid": 1406, + "disclosureURL": "https://markappmedia.site/vendor.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index 24eacb9bc79..d5fc01d7c33 100644 --- a/metadata/modules/holidBidAdapter.json +++ b/metadata/modules/holidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ads.holid.io/devicestorage.json": { - "timestamp": "2025-11-19T20:51:27.109Z", + "timestamp": "2026-03-02T14:45:50.576Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index 29aab4a9ff7..efc1583902c 100644 --- a/metadata/modules/hybridBidAdapter.json +++ b/metadata/modules/hybridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:27.382Z", + "timestamp": "2026-03-02T14:45:50.868Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index bbc919ef861..886832932a7 100644 --- a/metadata/modules/id5IdSystem.json +++ b/metadata/modules/id5IdSystem.json @@ -2,8 +2,250 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://id5-sync.com/tcf/disclosures.json": { - "timestamp": "2025-11-19T20:51:27.667Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:51.113Z", + "disclosures": [ + { + "identifier": "id5id", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_exp", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_last", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_last_exp", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_consent_data", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_consent_data_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_pd_{partnerId}", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_pd_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_pd", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_privacy", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_privacy_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_{partnerId}_nb", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_{partnerId}_nb_exp", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_v2_{cacheId}", + "type": "web", + "maxAgeSeconds": 1296000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_v2_signature", + "type": "web", + "maxAgeSeconds": 1296000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_extensions", + "type": "web", + "maxAgeSeconds": 28800, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_segments_{partnerId}", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_segments_{partnerId}_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5_trueLink_privacy", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-ts", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-redirect-timestamp", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-redirect-fail", + "type": "cookie", + "maxAgeSeconds": 604800, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-optout", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-true-link", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-true-link-refresh", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-true-link-refresh-exp", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + } + ] } }, "components": [ diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index 1e959d69d9b..03bcaedd660 100644 --- a/metadata/modules/identityLinkIdSystem.json +++ b/metadata/modules/identityLinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.ats.rlcdn.com/device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:27.944Z", + "timestamp": "2026-03-02T14:45:51.402Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index f75633254dd..eadb5eea6ca 100644 --- a/metadata/modules/illuminBidAdapter.json +++ b/metadata/modules/illuminBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admanmedia.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:27.970Z", + "timestamp": "2026-03-02T14:45:51.424Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index de6ab2e3d5e..a84d454fd03 100644 --- a/metadata/modules/impactifyBidAdapter.json +++ b/metadata/modules/impactifyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.impactify.io/tcfvendors.json": { - "timestamp": "2025-11-19T20:51:28.316Z", + "timestamp": "2026-03-02T14:45:51.728Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 7da0a1c40e4..a6e1e421e03 100644 --- a/metadata/modules/improvedigitalBidAdapter.json +++ b/metadata/modules/improvedigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2025-11-19T20:51:28.592Z", + "timestamp": "2026-03-02T14:45:52.052Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index bade3996e48..818bc7ee34a 100644 --- a/metadata/modules/inmobiBidAdapter.json +++ b/metadata/modules/inmobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publisher.inmobi.com/public/disclosure": { - "timestamp": "2025-11-19T20:51:28.593Z", + "timestamp": "2026-03-02T14:45:52.052Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index 9678d9143ca..93ac9d8e09a 100644 --- a/metadata/modules/insticatorBidAdapter.json +++ b/metadata/modules/insticatorBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.insticator.com/iab/device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:28.624Z", + "timestamp": "2026-03-02T14:45:52.089Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/insuradsBidAdapter.json b/metadata/modules/insuradsBidAdapter.json new file mode 100644 index 00000000000..05a0e236aad --- /dev/null +++ b/metadata/modules/insuradsBidAdapter.json @@ -0,0 +1,45 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://www.insurads.com/tcf-vdsod.json": { + "timestamp": "2026-03-02T14:45:52.162Z", + "disclosures": [ + { + "identifier": "___iat_ses", + "type": "cookie", + "maxAgeSeconds": 1800, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 7, + 9, + 10 + ] + }, + { + "identifier": "___iat_vis", + "type": "cookie", + "maxAgeSeconds": 15552000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 7, + 9, + 10 + ] + } + ] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "insurads", + "aliasOf": null, + "gvlid": 596, + "disclosureURL": "https://www.insurads.com/tcf-vdsod.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 7dfe9d8f578..43fe76d2cad 100644 --- a/metadata/modules/intentIqIdSystem.json +++ b/metadata/modules/intentIqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://agent.intentiq.com/GDPR/gdpr.json": { - "timestamp": "2025-11-19T20:51:28.651Z", + "timestamp": "2026-03-02T14:45:52.351Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index 7ea4211bf8e..9b23ea0bc54 100644 --- a/metadata/modules/invibesBidAdapter.json +++ b/metadata/modules/invibesBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.invibes.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:28.710Z", + "timestamp": "2026-03-02T14:45:52.421Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index cfd02b7360c..7f9b4c104c2 100644 --- a/metadata/modules/ipromBidAdapter.json +++ b/metadata/modules/ipromBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://core.iprom.net/info/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:29.106Z", + "timestamp": "2026-03-02T14:45:52.785Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index b45469e19ec..ba7581331d0 100644 --- a/metadata/modules/ixBidAdapter.json +++ b/metadata/modules/ixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.indexexchange.com/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:29.586Z", + "timestamp": "2026-03-02T14:45:53.259Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index 8526c632c84..f20fbceea31 100644 --- a/metadata/modules/justIdSystem.json +++ b/metadata/modules/justIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audience-solutions.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:29.770Z", + "timestamp": "2026-03-02T14:45:53.334Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index c7e37ae5747..7fb5438d310 100644 --- a/metadata/modules/justpremiumBidAdapter.json +++ b/metadata/modules/justpremiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.justpremium.com/devicestoragedisclosures.json": { - "timestamp": "2025-11-19T20:51:30.293Z", + "timestamp": "2026-03-02T14:45:53.863Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 03601c43bf2..6695015fb1e 100644 --- a/metadata/modules/jwplayerBidAdapter.json +++ b/metadata/modules/jwplayerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.jwplayer.com/devicestorage.json": { - "timestamp": "2025-11-19T20:51:30.311Z", + "timestamp": "2026-03-02T14:45:53.882Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index dc67cb78e6d..18c7713446a 100644 --- a/metadata/modules/kargoBidAdapter.json +++ b/metadata/modules/kargoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://storage.cloud.kargo.com/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:30.882Z", + "timestamp": "2026-03-02T14:45:54.165Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index 7adb8674f49..38316d32257 100644 --- a/metadata/modules/kueezRtbBidAdapter.json +++ b/metadata/modules/kueezRtbBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.kueez.com/tcf.json": { - "timestamp": "2025-11-19T20:51:30.901Z", + "timestamp": "2026-03-02T14:45:54.193Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/leagueMBidAdapter.json b/metadata/modules/leagueMBidAdapter.json new file mode 100644 index 00000000000..f4b02875bbb --- /dev/null +++ b/metadata/modules/leagueMBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "leagueM", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index 659ce95ef58..14e44dfc1e0 100644 --- a/metadata/modules/limelightDigitalBidAdapter.json +++ b/metadata/modules/limelightDigitalBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://policy.iion.io/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:30.975Z", + "timestamp": "2026-03-02T14:45:54.240Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:31.006Z", + "timestamp": "2026-03-02T14:45:54.278Z", "disclosures": [] } }, @@ -32,13 +32,6 @@ "gvlid": 1358, "disclosureURL": "https://policy.iion.io/deviceStorage.json" }, - { - "componentType": "bidder", - "componentName": "apester", - "aliasOf": "limelightDigital", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "adsyield", @@ -104,28 +97,35 @@ }, { "componentType": "bidder", - "componentName": "adnimation", + "componentName": "rtbdemand", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "rtbdemand", + "componentName": "altstar", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "altstar", + "componentName": "vaayaMedia", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null }, { "componentType": "bidder", - "componentName": "vaayaMedia", + "componentName": "performist", + "aliasOf": "limelightDigital", + "gvlid": null, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "oveeo", "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index 6239d4a31e8..73069893950 100644 --- a/metadata/modules/liveIntentIdSystem.json +++ b/metadata/modules/liveIntentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:31.007Z", + "timestamp": "2026-03-02T14:45:54.278Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index 6ff7475fe19..8c36b5b69d2 100644 --- a/metadata/modules/liveIntentRtdProvider.json +++ b/metadata/modules/liveIntentRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:31.046Z", + "timestamp": "2026-03-02T14:45:54.418Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 00b970f6826..a5fb8083be9 100644 --- a/metadata/modules/livewrappedBidAdapter.json +++ b/metadata/modules/livewrappedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://content.lwadm.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:31.047Z", + "timestamp": "2026-03-02T14:45:54.419Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/locIdSystem.json b/metadata/modules/locIdSystem.json new file mode 100644 index 00000000000..87e8fc03475 --- /dev/null +++ b/metadata/modules/locIdSystem.json @@ -0,0 +1,20 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "userId", + "componentName": "locId", + "gvlid": null, + "disclosureURL": null, + "aliasOf": null + }, + { + "componentType": "userId", + "componentName": "locid", + "gvlid": null, + "disclosureURL": null, + "aliasOf": "locId" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index 219e66cd8b5..33632aaca12 100644 --- a/metadata/modules/loopmeBidAdapter.json +++ b/metadata/modules/loopmeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://co.loopme.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:31.069Z", + "timestamp": "2026-03-02T14:45:54.453Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 46314251fc3..1a84524e9d6 100644 --- a/metadata/modules/lotamePanoramaIdSystem.json +++ b/metadata/modules/lotamePanoramaIdSystem.json @@ -2,19 +2,118 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tags.crwdcntrl.net/privacy/tcf-purposes.json": { - "timestamp": "2025-11-19T20:51:31.214Z", + "timestamp": "2026-03-02T14:45:54.576Z", "disclosures": [ + { + "identifier": "_cc_id", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "_cc_cc", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "_cc_aud", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "lotame_domain_check", + "type": "cookie", + "maxAgeSeconds": 10, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "_pubcid", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, { "identifier": "panoramaId", "type": "web", "purposes": [ 1, + 2, 3, + 4, 5, + 6, 7, 8, 9, - 10 + 10, + 11 ] }, { @@ -22,12 +121,16 @@ "type": "web", "purposes": [ 1, + 2, 3, + 4, 5, + 6, 7, 8, 9, - 10 + 10, + 11 ] }, { @@ -35,12 +138,16 @@ "type": "web", "purposes": [ 1, + 2, 3, + 4, 5, + 6, 7, 8, 9, - 10 + 10, + 11 ] }, { @@ -48,12 +155,16 @@ "type": "web", "purposes": [ 1, + 2, 3, + 4, 5, + 6, 7, 8, 9, - 10 + 10, + 11 ] }, { @@ -61,12 +172,16 @@ "type": "web", "purposes": [ 1, + 2, 3, + 4, 5, + 6, 7, 8, 9, - 10 + 10, + 11 ] }, { @@ -74,12 +189,33 @@ "type": "web", "purposes": [ 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "_pubcid", + "type": "web", + "purposes": [ + 1, + 2, 3, + 4, 5, + 6, 7, 8, 9, - 10 + 10, + 11 ] } ] diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 797934db108..96e987d9dc2 100644 --- a/metadata/modules/luponmediaBidAdapter.json +++ b/metadata/modules/luponmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://luponmedia.com/vendor_device_storage.json": { - "timestamp": "2025-11-19T20:51:31.229Z", + "timestamp": "2026-03-02T14:45:54.640Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 760afedd2cc..096ca78f06b 100644 --- a/metadata/modules/madvertiseBidAdapter.json +++ b/metadata/modules/madvertiseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.bluestack.app/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:31.756Z", + "timestamp": "2026-03-02T14:45:55.051Z", "disclosures": [] } }, diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index 616c4e5f5cf..e20881844c8 100644 --- a/metadata/modules/marsmediaBidAdapter.json +++ b/metadata/modules/marsmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mars.media/apis/tcf-v2.json": { - "timestamp": "2025-11-19T20:51:32.115Z", + "timestamp": "2026-03-02T14:45:55.409Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index 21f9a7ad631..19b9a989b2c 100644 --- a/metadata/modules/mediaConsortiumBidAdapter.json +++ b/metadata/modules/mediaConsortiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.hubvisor.io/assets/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:32.236Z", + "timestamp": "2026-03-02T14:45:55.518Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index a5d933f8cc5..43aee7b5d99 100644 --- a/metadata/modules/mediaforceBidAdapter.json +++ b/metadata/modules/mediaforceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://comparisons.org/privacy.json": { - "timestamp": "2025-11-19T20:51:32.366Z", + "timestamp": "2026-03-02T14:45:55.650Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index fbe6c0b1f36..64dfcf767a5 100644 --- a/metadata/modules/mediafuseBidAdapter.json +++ b/metadata/modules/mediafuseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2025-11-19T20:51:32.385Z", + "timestamp": "2026-03-02T14:45:55.669Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index 11856c9c3c2..ffa894357ad 100644 --- a/metadata/modules/mediagoBidAdapter.json +++ b/metadata/modules/mediagoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.mediago.io/js/tcf.json": { - "timestamp": "2025-11-19T20:51:32.386Z", + "timestamp": "2026-03-02T14:45:55.670Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index c1866cdc1f0..c41604e62be 100644 --- a/metadata/modules/mediakeysBidAdapter.json +++ b/metadata/modules/mediakeysBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.eu-west-3.amazonaws.com/adserving.resourcekeys.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:32.459Z", + "timestamp": "2026-03-02T14:45:55.769Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index 4765a26023d..b62853ac937 100644 --- a/metadata/modules/medianetBidAdapter.json +++ b/metadata/modules/medianetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.media.net/tcfv2/gvl/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:32.743Z", + "timestamp": "2026-03-02T14:45:56.051Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:32.879Z", + "timestamp": "2026-03-02T14:45:56.188Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index 3ab9de062b7..b3c225207bc 100644 --- a/metadata/modules/mediasquareBidAdapter.json +++ b/metadata/modules/mediasquareBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediasquare.fr/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:32.917Z", + "timestamp": "2026-03-02T14:45:56.239Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index 11ba1aedc46..d6da81c2fe5 100644 --- a/metadata/modules/mgidBidAdapter.json +++ b/metadata/modules/mgidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2025-11-19T20:51:33.449Z", + "timestamp": "2026-03-02T14:45:56.794Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index b627ea6172a..d6dd6937212 100644 --- a/metadata/modules/mgidRtdProvider.json +++ b/metadata/modules/mgidRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2025-11-19T20:51:33.545Z", + "timestamp": "2026-03-02T14:45:56.864Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index 729e83d291c..d0da32736ce 100644 --- a/metadata/modules/mgidXBidAdapter.json +++ b/metadata/modules/mgidXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2025-11-19T20:51:33.545Z", + "timestamp": "2026-03-02T14:45:56.864Z", "disclosures": [] } }, diff --git a/metadata/modules/mileBidAdapter.json b/metadata/modules/mileBidAdapter.json new file mode 100644 index 00000000000..ba3e8b683fc --- /dev/null +++ b/metadata/modules/mileBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "mile", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index 98998cf1ad3..9908f3b78ee 100644 --- a/metadata/modules/minutemediaBidAdapter.json +++ b/metadata/modules/minutemediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://disclosures.mmctsvc.com/device-storage.json": { - "timestamp": "2025-11-19T20:51:33.546Z", + "timestamp": "2026-03-02T14:45:56.865Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index 4d433c3a2bf..14a16812ce4 100644 --- a/metadata/modules/missenaBidAdapter.json +++ b/metadata/modules/missenaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.missena.io/iab.json": { - "timestamp": "2025-11-19T20:51:33.577Z", + "timestamp": "2026-03-02T14:45:56.886Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index 03233c61c19..1ce8457b058 100644 --- a/metadata/modules/mobianRtdProvider.json +++ b/metadata/modules/mobianRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.outcomes.net/tcf.json": { - "timestamp": "2025-11-19T20:51:33.631Z", + "timestamp": "2026-03-02T14:45:56.944Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index 73706e77eb1..3982ab341b1 100644 --- a/metadata/modules/mobkoiBidAdapter.json +++ b/metadata/modules/mobkoiBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:33.651Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:57.043Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index 3283be58b12..8f9ed0091ef 100644 --- a/metadata/modules/mobkoiIdSystem.json +++ b/metadata/modules/mobkoiIdSystem.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:33.671Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:57.065Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index 083a53a9935..8ac1b408eae 100644 --- a/metadata/modules/msftBidAdapter.json +++ b/metadata/modules/msftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2025-11-19T20:51:33.671Z", + "timestamp": "2026-03-02T14:45:57.066Z", "disclosures": [] } }, diff --git a/metadata/modules/mycodemediaBidAdapter.json b/metadata/modules/mycodemediaBidAdapter.json new file mode 100644 index 00000000000..6fdf70e5b1f --- /dev/null +++ b/metadata/modules/mycodemediaBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "mycodemedia", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index e4f1a7a04ed..2a3f0c5c99a 100644 --- a/metadata/modules/nativeryBidAdapter.json +++ b/metadata/modules/nativeryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnimg.nativery.com/widget/js/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:33.672Z", + "timestamp": "2026-03-02T14:45:57.067Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index 36f38526ff4..59662e64c8a 100644 --- a/metadata/modules/nativoBidAdapter.json +++ b/metadata/modules/nativoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.nativo.com/tcf-disclosures.json": { - "timestamp": "2025-11-19T20:51:34.024Z", + "timestamp": "2026-03-02T14:45:57.381Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index 1d072cda0f9..57fd3223351 100644 --- a/metadata/modules/newspassidBidAdapter.json +++ b/metadata/modules/newspassidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2025-11-19T20:51:34.046Z", + "timestamp": "2026-03-02T14:45:57.429Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index f647c5dee6b..d57a7a52695 100644 --- a/metadata/modules/nextMillenniumBidAdapter.json +++ b/metadata/modules/nextMillenniumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://nextmillennium.io/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:34.047Z", + "timestamp": "2026-03-02T14:45:57.430Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index e6a4502aa76..f366a7a8120 100644 --- a/metadata/modules/nextrollBidAdapter.json +++ b/metadata/modules/nextrollBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.adroll.com/shares/device_storage.json": { - "timestamp": "2025-11-19T20:51:34.118Z", + "timestamp": "2026-03-02T14:45:57.516Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 902e839f5cd..00c1ef09267 100644 --- a/metadata/modules/nexx360BidAdapter.json +++ b/metadata/modules/nexx360BidAdapter.json @@ -2,19 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://fast.nexx360.io/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:34.571Z", + "timestamp": "2026-03-02T14:45:58.404Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2025-11-19T20:51:34.414Z", + "timestamp": "2026-03-02T14:45:57.804Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:34.441Z", + "timestamp": "2026-03-02T14:45:57.826Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:34.571Z", + "timestamp": "2026-03-02T14:45:57.949Z", "disclosures": [ { "identifier": "glomexUser", @@ -45,12 +45,8 @@ } ] }, - "https://mediafuse.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:34.571Z", - "disclosures": [] - }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2025-11-19T20:51:34.657Z", + "timestamp": "2026-03-02T14:45:57.949Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -65,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2025-11-19T20:51:34.700Z", + "timestamp": "2026-03-02T14:45:58.021Z", "disclosures": [] } }, @@ -175,13 +171,6 @@ "gvlid": 967, "disclosureURL": "https://player.glomex.com/.well-known/deviceStorage.json" }, - { - "componentType": "bidder", - "componentName": "revnew", - "aliasOf": "nexx360", - "gvlid": 1468, - "disclosureURL": "https://mediafuse.com/deviceStorage.json" - }, { "componentType": "bidder", "componentName": "pubxai", @@ -195,6 +184,13 @@ "aliasOf": "nexx360", "gvlid": 1253, "disclosureURL": "https://yieldbird.com/devicestorage.json" + }, + { + "componentType": "bidder", + "componentName": "netads", + "aliasOf": "nexx360", + "gvlid": 965, + "disclosureURL": "https://fast.nexx360.io/deviceStorage.json" } ] } \ No newline at end of file diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index 860d8b6a786..056f7d06193 100644 --- a/metadata/modules/nobidBidAdapter.json +++ b/metadata/modules/nobidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://public.servenobid.com/gdpr_tcf/vendor_device_storage_operational_disclosures.json": { - "timestamp": "2025-11-19T20:51:35.069Z", + "timestamp": "2026-03-02T14:45:58.405Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 80e71b663ae..3c0a03590fe 100644 --- a/metadata/modules/nodalsAiRtdProvider.json +++ b/metadata/modules/nodalsAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.nodals.ai/vendor.json": { - "timestamp": "2025-11-19T20:51:35.089Z", + "timestamp": "2026-03-02T14:45:58.420Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index 091a3406069..704ad832740 100644 --- a/metadata/modules/novatiqIdSystem.json +++ b/metadata/modules/novatiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://novatiq.com/privacy/iab/novatiq.json": { - "timestamp": "2025-11-19T20:51:36.315Z", + "timestamp": "2026-03-02T14:46:00.252Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index cf40ea10f04..e0eda9c248a 100644 --- a/metadata/modules/oguryBidAdapter.json +++ b/metadata/modules/oguryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.ogury.co/disclosure.json": { - "timestamp": "2025-11-19T20:51:36.644Z", + "timestamp": "2026-03-02T14:46:00.621Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index 9a67d3c863d..b9cfde98549 100644 --- a/metadata/modules/omnidexBidAdapter.json +++ b/metadata/modules/omnidexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.omni-dex.io/devicestorage.json": { - "timestamp": "2025-11-19T20:51:36.709Z", + "timestamp": "2026-03-02T14:46:00.670Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index e802bd7f5fd..bf3d6e763b9 100644 --- a/metadata/modules/omsBidAdapter.json +++ b/metadata/modules/omsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2025-11-19T20:51:36.804Z", + "timestamp": "2026-03-02T14:46:00.723Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 8db87fd9a54..94e1cbede34 100644 --- a/metadata/modules/onetagBidAdapter.json +++ b/metadata/modules/onetagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://onetag-cdn.com/privacy/tcf_storage.json": { - "timestamp": "2025-11-19T20:51:36.805Z", + "timestamp": "2026-03-02T14:46:00.724Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index 950b15d5a40..b4ee396237c 100644 --- a/metadata/modules/openwebBidAdapter.json +++ b/metadata/modules/openwebBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2025-11-19T20:51:37.157Z", + "timestamp": "2026-03-02T14:46:01.014Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index bbaade2d23e..dfb10023709 100644 --- a/metadata/modules/openxBidAdapter.json +++ b/metadata/modules/openxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.openx.com/device-storage.json": { - "timestamp": "2025-11-19T20:51:37.191Z", + "timestamp": "2026-03-02T14:46:01.052Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index 311a6d714cb..be92865dfd2 100644 --- a/metadata/modules/operaadsBidAdapter.json +++ b/metadata/modules/operaadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://res.adx.opera.com/dsd.json": { - "timestamp": "2025-11-19T20:51:37.267Z", + "timestamp": "2026-03-02T14:46:01.082Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index 7258949c64c..51ed301458d 100644 --- a/metadata/modules/optidigitalBidAdapter.json +++ b/metadata/modules/optidigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://scripts.opti-digital.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:37.295Z", + "timestamp": "2026-03-02T14:46:01.269Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index eed3e4d239c..fef02111513 100644 --- a/metadata/modules/optoutBidAdapter.json +++ b/metadata/modules/optoutBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserving.optoutadvertising.com/dsd": { - "timestamp": "2025-11-19T20:51:37.340Z", + "timestamp": "2026-03-02T14:46:01.427Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index b9df0f08646..b9571894c8a 100644 --- a/metadata/modules/orbidderBidAdapter.json +++ b/metadata/modules/orbidderBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://orbidder.otto.de/disclosure/dsd.json": { - "timestamp": "2025-11-19T20:51:37.600Z", + "timestamp": "2026-03-02T14:46:01.687Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index 9f2cba4f046..325c70c587c 100644 --- a/metadata/modules/outbrainBidAdapter.json +++ b/metadata/modules/outbrainBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.outbrain.com/privacy/wp-json/privacy/v2/devicestorage.json": { - "timestamp": "2025-11-19T20:51:37.889Z", + "timestamp": "2026-03-02T14:46:02.006Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index e5de08e8dfd..23d5ad8eb09 100644 --- a/metadata/modules/ozoneBidAdapter.json +++ b/metadata/modules/ozoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://prebid.the-ozone-project.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:38.077Z", + "timestamp": "2026-03-02T14:46:02.249Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index fbfb0d29fc8..d4c804fcc8c 100644 --- a/metadata/modules/pairIdSystem.json +++ b/metadata/modules/pairIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.gstatic.com/iabtcf/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:38.338Z", + "timestamp": "2026-03-02T14:46:02.471Z", "disclosures": [ { "identifier": "__gads", @@ -224,7 +224,7 @@ "cookieRefresh": false }, { - "identifier": "_gcl_ag", + "identifier": "_gcl_gs", "type": "cookie", "maxAgeSeconds": 7776000, "purposes": [ @@ -239,7 +239,7 @@ "cookieRefresh": false }, { - "identifier": "_gcl_gs", + "identifier": "_gcl_ag", "type": "cookie", "maxAgeSeconds": 7776000, "purposes": [ @@ -297,6 +297,21 @@ 10 ], "cookieRefresh": false + }, + { + "identifier": "FPGCLGS", + "type": "cookie", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 2, + 3, + 4, + 7, + 9, + 10 + ], + "cookieRefresh": false } ] } diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json new file mode 100644 index 00000000000..e06de639217 --- /dev/null +++ b/metadata/modules/panxoBidAdapter.json @@ -0,0 +1,46 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://cdn.panxo.ai/tcf/device-storage.json": { + "timestamp": "2026-03-02T14:46:02.495Z", + "disclosures": [ + { + "identifier": "panxo_uid", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "panxo_aso_sync", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "panxo_debug", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1 + ] + } + ] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "panxo", + "aliasOf": null, + "gvlid": 1527, + "disclosureURL": "https://cdn.panxo.ai/tcf/device-storage.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/panxoRtdProvider.json b/metadata/modules/panxoRtdProvider.json new file mode 100644 index 00000000000..5ad682b104d --- /dev/null +++ b/metadata/modules/panxoRtdProvider.json @@ -0,0 +1,12 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "rtd", + "componentName": "panxo", + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index 4a45428b467..5bdea8a1dd9 100644 --- a/metadata/modules/performaxBidAdapter.json +++ b/metadata/modules/performaxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.performax.cz/device_storage.json": { - "timestamp": "2025-11-19T20:51:38.364Z", + "timestamp": "2026-03-02T14:46:02.691Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index e3b82502b76..647b813dccd 100644 --- a/metadata/modules/permutiveIdentityManagerIdSystem.json +++ b/metadata/modules/permutiveIdentityManagerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2025-11-19T20:51:38.784Z", + "timestamp": "2026-03-02T14:46:03.107Z", "disclosures": [ { "identifier": "_pdfps", @@ -278,7 +278,7 @@ { "componentType": "userId", "componentName": "permutiveIdentityManagerId", - "gvlid": 361, + "gvlid": null, "disclosureURL": "https://assets.permutive.app/tcf/tcf.json", "aliasOf": null } diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 14fedd6d831..0f41067e138 100644 --- a/metadata/modules/permutiveRtdProvider.json +++ b/metadata/modules/permutiveRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2025-11-19T20:51:39.019Z", + "timestamp": "2026-03-02T14:46:03.283Z", "disclosures": [ { "identifier": "_pdfps", @@ -278,7 +278,7 @@ { "componentType": "rtd", "componentName": "permutive", - "gvlid": 361, + "gvlid": null, "disclosureURL": "https://assets.permutive.app/tcf/tcf.json" } ] diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index b37cd00a26b..c04bfe3f164 100644 --- a/metadata/modules/pixfutureBidAdapter.json +++ b/metadata/modules/pixfutureBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.pixfuture.com/vendor-disclosures.json": { - "timestamp": "2025-11-19T20:51:39.021Z", + "timestamp": "2026-03-02T14:46:03.285Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index 60381299bfe..56ff6f0ebc5 100644 --- a/metadata/modules/playdigoBidAdapter.json +++ b/metadata/modules/playdigoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://playdigo.com/file.json": { - "timestamp": "2025-11-19T20:51:39.074Z", + "timestamp": "2026-03-02T14:46:03.345Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 7fbdf54ee46..3ef5aaa70c7 100644 --- a/metadata/modules/prebid-core.json +++ b/metadata/modules/prebid-core.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/probes.json": { - "timestamp": "2025-11-19T20:51:08.091Z", + "timestamp": "2026-03-02T14:44:46.315Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2025-11-19T20:51:08.092Z", + "timestamp": "2026-03-02T14:44:46.315Z", "disclosures": [ { "identifier": "__*_debugging__", @@ -41,6 +41,11 @@ "componentName": "fpdEnrichment", "disclosureURL": "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/probes.json" }, + { + "componentType": "prebid", + "componentName": "storage", + "disclosureURL": "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/probes.json" + }, { "componentType": "prebid", "componentName": "debugging", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index e05b75bd550..63ae55fec56 100644 --- a/metadata/modules/precisoBidAdapter.json +++ b/metadata/modules/precisoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://preciso.net/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:39.262Z", + "timestamp": "2026-03-02T14:46:03.527Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index b9873fe45c5..b1668166cfa 100644 --- a/metadata/modules/prismaBidAdapter.json +++ b/metadata/modules/prismaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://fast.nexx360.io/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:39.489Z", + "timestamp": "2026-03-02T14:46:03.818Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index fcbe8780460..e88a5f370ba 100644 --- a/metadata/modules/programmaticXBidAdapter.json +++ b/metadata/modules/programmaticXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://progrtb.com/tcf-vendor-disclosures.json": { - "timestamp": "2025-11-19T20:51:39.490Z", + "timestamp": "2026-03-02T14:46:03.818Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 1744e2edd89..905a96a3b49 100644 --- a/metadata/modules/proxistoreBidAdapter.json +++ b/metadata/modules/proxistoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://abs.proxistore.com/assets/json/proxistore_device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:39.534Z", + "timestamp": "2026-03-02T14:46:03.882Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index 4c7748afbf2..7ad66a8914c 100644 --- a/metadata/modules/publinkIdSystem.json +++ b/metadata/modules/publinkIdSystem.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:39.911Z", + "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { + "timestamp": "2026-03-02T14:46:04.343Z", "disclosures": [ { "identifier": "dtm_status", @@ -582,7 +582,7 @@ "componentType": "userId", "componentName": "publinkId", "gvlid": 24, - "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json", + "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json", "aliasOf": null } ] diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index 2884bce73ef..7dc64f1d033 100644 --- a/metadata/modules/pubmaticBidAdapter.json +++ b/metadata/modules/pubmaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2025-11-19T20:51:39.912Z", + "timestamp": "2026-03-02T14:46:04.343Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index 7883f89271a..2545e56212f 100644 --- a/metadata/modules/pubmaticIdSystem.json +++ b/metadata/modules/pubmaticIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2025-11-19T20:51:39.955Z", + "timestamp": "2026-03-02T14:46:04.569Z", "disclosures": [] } }, diff --git a/metadata/modules/pubstackBidAdapter.json b/metadata/modules/pubstackBidAdapter.json new file mode 100644 index 00000000000..5081e22a00d --- /dev/null +++ b/metadata/modules/pubstackBidAdapter.json @@ -0,0 +1,25 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json": { + "timestamp": "2026-03-02T14:46:04.602Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "pubstack", + "aliasOf": null, + "gvlid": 1408, + "disclosureURL": "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json" + }, + { + "componentType": "bidder", + "componentName": "pubstack_server", + "aliasOf": "pubstack", + "gvlid": 1408, + "disclosureURL": "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index 0fcc3d50066..3134a58466f 100644 --- a/metadata/modules/pulsepointBidAdapter.json +++ b/metadata/modules/pulsepointBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bh.contextweb.com/tcf/vendorInfo.json": { - "timestamp": "2025-11-19T20:51:39.957Z", + "timestamp": "2026-03-02T14:46:04.603Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index 52da89d65fe..0b8de794a6c 100644 --- a/metadata/modules/quantcastBidAdapter.json +++ b/metadata/modules/quantcastBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.quantcast.com/.well-known/devicestorage.json": { - "timestamp": "2025-11-19T20:51:39.976Z", + "timestamp": "2026-03-02T14:46:04.619Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json index af2c2e18ed2..0ac7d60db4a 100644 --- a/metadata/modules/quantcastIdSystem.json +++ b/metadata/modules/quantcastIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.quantcast.com/.well-known/devicestorage.json": { - "timestamp": "2025-11-19T20:51:40.184Z", + "timestamp": "2026-03-02T14:46:04.798Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index d01e396cf85..673069e3845 100644 --- a/metadata/modules/r2b2BidAdapter.json +++ b/metadata/modules/r2b2BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.r2b2.io/cookie_disclosure": { - "timestamp": "2025-11-19T20:51:40.185Z", + "timestamp": "2026-03-02T14:46:04.800Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index 84a63dfce45..752bc1a2f76 100644 --- a/metadata/modules/readpeakBidAdapter.json +++ b/metadata/modules/readpeakBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.readpeak.com/tcf/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:40.573Z", + "timestamp": "2026-03-02T14:46:05.271Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index e7400919ba9..622cc457012 100644 --- a/metadata/modules/relayBidAdapter.json +++ b/metadata/modules/relayBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://relay42.com/hubfs/raw_assets/public/IAB.json": { - "timestamp": "2025-11-19T20:51:40.603Z", - "disclosures": [] + "timestamp": "2026-03-02T14:46:05.294Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index c6676bc9a18..5addfe8ad58 100644 --- a/metadata/modules/relevantdigitalBidAdapter.json +++ b/metadata/modules/relevantdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.relevant-digital.com/resources/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:40.699Z", + "timestamp": "2026-03-02T14:46:06.100Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index 320913ab3d3..dd359941cee 100644 --- a/metadata/modules/resetdigitalBidAdapter.json +++ b/metadata/modules/resetdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resetdigital.co/GDPR-TCF.json": { - "timestamp": "2025-11-19T20:51:40.878Z", + "timestamp": "2026-03-02T14:46:06.261Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index 9fa482f8c47..37dc05e23e9 100644 --- a/metadata/modules/responsiveAdsBidAdapter.json +++ b/metadata/modules/responsiveAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publish.responsiveads.com/tcf/tcf-v2.json": { - "timestamp": "2025-11-19T20:51:40.922Z", + "timestamp": "2026-03-02T14:46:06.304Z", "disclosures": [] } }, diff --git a/metadata/modules/revantageBidAdapter.json b/metadata/modules/revantageBidAdapter.json new file mode 100644 index 00000000000..90eda1e36ad --- /dev/null +++ b/metadata/modules/revantageBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "revantage", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index c85e30b1da9..4c36e64fbd2 100644 --- a/metadata/modules/revcontentBidAdapter.json +++ b/metadata/modules/revcontentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sothebys.revcontent.com/static/device_storage.json": { - "timestamp": "2025-11-19T20:51:40.952Z", + "timestamp": "2026-03-02T14:46:06.345Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json new file mode 100644 index 00000000000..6a2c2b0b465 --- /dev/null +++ b/metadata/modules/revnewBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://mediafuse.com/deviceStorage.json": { + "timestamp": "2026-03-02T14:46:06.391Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "revnew", + "aliasOf": null, + "gvlid": 1468, + "disclosureURL": "https://mediafuse.com/deviceStorage.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index 8a309ace5cf..b15321eabee 100644 --- a/metadata/modules/rhythmoneBidAdapter.json +++ b/metadata/modules/rhythmoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:40.980Z", + "timestamp": "2026-03-02T14:46:06.448Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index 4b9eafb1589..10611865e4a 100644 --- a/metadata/modules/richaudienceBidAdapter.json +++ b/metadata/modules/richaudienceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnj.richaudience.com/52a26ab9400b2a9f5aabfa20acf3196g.json": { - "timestamp": "2025-11-19T20:51:41.216Z", + "timestamp": "2026-03-02T14:46:06.805Z", "disclosures": [] } }, diff --git a/metadata/modules/ringieraxelspringerBidAdapter.json b/metadata/modules/ringieraxelspringerBidAdapter.json index 8ad5d4bffce..1ca1c72523f 100644 --- a/metadata/modules/ringieraxelspringerBidAdapter.json +++ b/metadata/modules/ringieraxelspringerBidAdapter.json @@ -4,10 +4,17 @@ "components": [ { "componentType": "bidder", - "componentName": "ringieraxelspringer", + "componentName": "das", "aliasOf": null, "gvlid": null, "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "ringieraxelspringer", + "aliasOf": "das", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index e83029082bb..3891bd224cb 100644 --- a/metadata/modules/riseBidAdapter.json +++ b/metadata/modules/riseBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d2pm7iglz0b6eq.cloudfront.net/RiseDeviceStorage.json": { - "timestamp": "2025-11-19T20:51:41.284Z", + "timestamp": "2026-03-02T14:46:06.861Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2025-11-19T20:51:41.284Z", + "timestamp": "2026-03-02T14:46:06.861Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index db0ff171189..980500fae02 100644 --- a/metadata/modules/rixengineBidAdapter.json +++ b/metadata/modules/rixengineBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.algorix.co/gdpr-disclosure.json": { - "timestamp": "2025-11-19T20:51:41.285Z", + "timestamp": "2026-03-02T14:46:06.862Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index 4af03548531..31714df7b2e 100644 --- a/metadata/modules/rtbhouseBidAdapter.json +++ b/metadata/modules/rtbhouseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://rtbhouse.com/DeviceStorage.json": { - "timestamp": "2025-11-19T20:51:41.308Z", + "timestamp": "2026-03-02T14:46:06.887Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index 4c0a7bea043..68ecbd8f4a9 100644 --- a/metadata/modules/rubiconBidAdapter.json +++ b/metadata/modules/rubiconBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { - "timestamp": "2025-11-19T20:51:41.772Z", + "timestamp": "2026-03-02T14:46:07.020Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index 435a1614543..b39b894bd9f 100644 --- a/metadata/modules/scaliburBidAdapter.json +++ b/metadata/modules/scaliburBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://legal.overwolf.com/docs/overwolf/website/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:42.056Z", + "timestamp": "2026-03-02T14:46:07.254Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 534b6c896fc..1772818cedc 100644 --- a/metadata/modules/screencoreBidAdapter.json +++ b/metadata/modules/screencoreBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://screencore.io/tcf.json": { - "timestamp": "2025-11-19T20:51:42.072Z", - "disclosures": null + "timestamp": "2026-03-02T14:46:07.274Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index fd098334269..801af2fb55e 100644 --- a/metadata/modules/seedingAllianceBidAdapter.json +++ b/metadata/modules/seedingAllianceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.nativendo.de/cdn/asset/tcf/purpose-specific-storage-and-access-information.json": { - "timestamp": "2025-11-19T20:51:44.670Z", + "timestamp": "2026-03-02T14:46:07.339Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index af146802a6b..3ad01be8a3a 100644 --- a/metadata/modules/seedtagBidAdapter.json +++ b/metadata/modules/seedtagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2025-11-19T20:51:44.716Z", + "timestamp": "2026-03-02T14:46:07.366Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index 9f3260038dd..296ab8e1865 100644 --- a/metadata/modules/semantiqRtdProvider.json +++ b/metadata/modules/semantiqRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2025-11-19T20:51:44.725Z", + "timestamp": "2026-03-02T14:46:07.366Z", "disclosures": [] } }, diff --git a/metadata/modules/setupadBidAdapter.json b/metadata/modules/setupadBidAdapter.json index 64902e02316..20bb4dab06f 100644 --- a/metadata/modules/setupadBidAdapter.json +++ b/metadata/modules/setupadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cookies.stpd.cloud/disclosures.json": { - "timestamp": "2025-11-19T20:51:44.797Z", + "timestamp": "2026-03-02T14:46:07.471Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index 37262a8bb45..4326d208d40 100644 --- a/metadata/modules/sevioBidAdapter.json +++ b/metadata/modules/sevioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sevio.com/tcf.json": { - "timestamp": "2025-11-19T20:51:44.964Z", + "timestamp": "2026-03-02T14:46:07.609Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index 103686eec7f..cda116dbd54 100644 --- a/metadata/modules/sharedIdSystem.json +++ b/metadata/modules/sharedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2025-11-19T20:51:45.132Z", + "timestamp": "2026-03-02T14:46:07.758Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index 10ae75097d0..8c2ce012b1d 100644 --- a/metadata/modules/sharethroughBidAdapter.json +++ b/metadata/modules/sharethroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.sharethrough.com/gvl.json": { - "timestamp": "2025-11-19T20:51:45.139Z", + "timestamp": "2026-03-02T14:46:07.758Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index a42de2663f7..9abcf1c23b4 100644 --- a/metadata/modules/showheroes-bsBidAdapter.json +++ b/metadata/modules/showheroes-bsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static-origin.showheroes.com/gvl_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:45.221Z", + "timestamp": "2026-03-02T14:46:07.777Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index d5a8d366403..c5aa4e9afcb 100644 --- a/metadata/modules/silvermobBidAdapter.json +++ b/metadata/modules/silvermobBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://silvermob.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:45.652Z", + "timestamp": "2026-03-02T14:46:08.247Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index a0000813db7..348ce7f9fd4 100644 --- a/metadata/modules/sirdataRtdProvider.json +++ b/metadata/modules/sirdataRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sirdata.eu/sirdata_device_storage_disclosure.json": { - "timestamp": "2025-11-19T20:51:45.668Z", + "timestamp": "2026-03-02T14:46:08.262Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index f78f52100c4..413fb0f4ca3 100644 --- a/metadata/modules/smaatoBidAdapter.json +++ b/metadata/modules/smaatoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resources.smaato.com/hubfs/Smaato/IAB/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:45.988Z", + "timestamp": "2026-03-02T14:46:08.585Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index 40f3a1a6e2d..a1c3212113e 100644 --- a/metadata/modules/smartadserverBidAdapter.json +++ b/metadata/modules/smartadserverBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2025-11-19T20:51:46.084Z", + "timestamp": "2026-03-02T14:46:08.665Z", "disclosures": [] } }, diff --git a/metadata/modules/smarthubBidAdapter.json b/metadata/modules/smarthubBidAdapter.json index eb77b60b1e7..7aff9b5ebd5 100644 --- a/metadata/modules/smarthubBidAdapter.json +++ b/metadata/modules/smarthubBidAdapter.json @@ -92,6 +92,13 @@ "aliasOf": "smarthub", "gvlid": null, "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "radiantfusion", + "aliasOf": "smarthub", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index fd7eabf9d7c..06854dd00c3 100644 --- a/metadata/modules/smartxBidAdapter.json +++ b/metadata/modules/smartxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.smartclip.net/iab/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:46.085Z", + "timestamp": "2026-03-02T14:46:08.666Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index 20ba7fece8e..202d8317bad 100644 --- a/metadata/modules/smartyadsBidAdapter.json +++ b/metadata/modules/smartyadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smartyads.com/tcf.json": { - "timestamp": "2025-11-19T20:51:46.102Z", + "timestamp": "2026-03-02T14:46:08.685Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index bba81b93490..7b14099448a 100644 --- a/metadata/modules/smilewantedBidAdapter.json +++ b/metadata/modules/smilewantedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smilewanted.com/vendor-device-storage-disclosures.json": { - "timestamp": "2025-11-19T20:51:46.145Z", + "timestamp": "2026-03-02T14:46:08.723Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index 13a8f3b522e..ef1115d5554 100644 --- a/metadata/modules/snigelBidAdapter.json +++ b/metadata/modules/snigelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.snigelweb.com/gvl/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:46.631Z", + "timestamp": "2026-03-02T14:46:09.181Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index 9972c24ccc8..b75cfe08e7c 100644 --- a/metadata/modules/sonaradsBidAdapter.json +++ b/metadata/modules/sonaradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bridgeupp.com/device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:46.676Z", + "timestamp": "2026-03-02T14:46:09.233Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index 45f0cfcd727..6ebb47d3e03 100644 --- a/metadata/modules/sonobiBidAdapter.json +++ b/metadata/modules/sonobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sonobi.com/tcf2-device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:46.917Z", + "timestamp": "2026-03-02T14:46:09.460Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index 72d52ec296c..0abfb9c0b85 100644 --- a/metadata/modules/sovrnBidAdapter.json +++ b/metadata/modules/sovrnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sovrn.com/tcf-cookie-disclosure/disclosure.json": { - "timestamp": "2025-11-19T20:51:47.152Z", + "timestamp": "2026-03-02T14:46:09.690Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index 8775c8fd162..b89e3d7a3d7 100644 --- a/metadata/modules/sparteoBidAdapter.json +++ b/metadata/modules/sparteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:47.212Z", + "timestamp": "2026-03-02T14:46:09.709Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index 9987d6d468e..be24aefdf2e 100644 --- a/metadata/modules/ssmasBidAdapter.json +++ b/metadata/modules/ssmasBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://semseoymas.com/iab.json": { - "timestamp": "2025-11-19T20:51:47.488Z", + "timestamp": "2026-03-02T14:46:09.989Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index 4b74ae80cf4..a99dc0c8ea5 100644 --- a/metadata/modules/sspBCBidAdapter.json +++ b/metadata/modules/sspBCBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:48.083Z", + "timestamp": "2026-03-02T14:46:10.636Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index 0c10acf9c32..6a53c79a6f2 100644 --- a/metadata/modules/stackadaptBidAdapter.json +++ b/metadata/modules/stackadaptBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.amazonaws.com/stackadapt_public/disclosures.json": { - "timestamp": "2025-11-19T20:51:48.083Z", + "timestamp": "2026-03-02T14:46:10.637Z", "disclosures": [ { "identifier": "sa-camp-*", @@ -62,6 +62,17 @@ 4 ] }, + { + "identifier": "sa-user-id-v4", + "type": "cookie", + "maxAgeSeconds": 31536000, + "cookieRefresh": false, + "purposes": [ + 1, + 3, + 4 + ] + }, { "identifier": "sa-user-id", "type": "web", @@ -84,6 +95,17 @@ 4 ] }, + { + "identifier": "sa-user-id-v4", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1, + 3, + 4 + ] + }, { "identifier": "sa-camp-*", "type": "web", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index 345eef10b8e..62dadaf9188 100644 --- a/metadata/modules/startioBidAdapter.json +++ b/metadata/modules/startioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://info.startappservice.com/tcf/start.io_domains.json": { - "timestamp": "2025-11-19T20:51:48.112Z", + "timestamp": "2026-03-02T14:46:10.672Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index 8979658efb2..4830347118e 100644 --- a/metadata/modules/stroeerCoreBidAdapter.json +++ b/metadata/modules/stroeerCoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.stroeer.de/StroeerSSP_deviceStorage.json": { - "timestamp": "2025-11-19T20:51:48.129Z", + "timestamp": "2026-03-02T14:46:10.688Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 431da5d0b5e..0409bb9984d 100644 --- a/metadata/modules/stvBidAdapter.json +++ b/metadata/modules/stvBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/stv.json": { - "timestamp": "2025-11-19T20:51:48.464Z", + "timestamp": "2026-03-02T14:46:11.041Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index ed5e5e42678..babdae509ce 100644 --- a/metadata/modules/sublimeBidAdapter.json +++ b/metadata/modules/sublimeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.ayads.co/cookiepolicy.json": { - "timestamp": "2025-11-19T20:51:49.146Z", + "timestamp": "2026-03-02T14:46:11.678Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index fb580391c59..c829cd46687 100644 --- a/metadata/modules/taboolaBidAdapter.json +++ b/metadata/modules/taboolaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2025-11-19T20:51:49.410Z", + "timestamp": "2026-03-02T14:46:11.938Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index 0a528a2a855..6fcf6a5ec28 100644 --- a/metadata/modules/taboolaIdSystem.json +++ b/metadata/modules/taboolaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2025-11-19T20:51:50.104Z", + "timestamp": "2026-03-02T14:46:12.149Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index c1eff5141fc..35fb4310c9c 100644 --- a/metadata/modules/tadvertisingBidAdapter.json +++ b/metadata/modules/tadvertisingBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.emetriq.de/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:50.104Z", + "timestamp": "2026-03-02T14:46:12.150Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index 0820c6e91d0..6a1843cea0d 100644 --- a/metadata/modules/tappxBidAdapter.json +++ b/metadata/modules/tappxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tappx.com/devicestorage.json": { - "timestamp": "2025-11-19T20:51:50.105Z", + "timestamp": "2026-03-02T14:46:12.408Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index d928341fda6..d785b33951a 100644 --- a/metadata/modules/targetVideoBidAdapter.json +++ b/metadata/modules/targetVideoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2025-11-19T20:51:50.138Z", + "timestamp": "2026-03-02T14:46:12.436Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index 0d1c9da97ec..669db4c09d6 100644 --- a/metadata/modules/teadsBidAdapter.json +++ b/metadata/modules/teadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:50.138Z", + "timestamp": "2026-03-02T14:46:12.437Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index b05bfee171a..a84486229b0 100644 --- a/metadata/modules/teadsIdSystem.json +++ b/metadata/modules/teadsIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:50.164Z", + "timestamp": "2026-03-02T14:46:12.457Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index f3000f6cc1d..fb6a14599bd 100644 --- a/metadata/modules/tealBidAdapter.json +++ b/metadata/modules/tealBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://c.bids.ws/iab/disclosures.json": { - "timestamp": "2025-11-19T20:51:50.165Z", + "timestamp": "2026-03-02T14:46:12.457Z", "disclosures": [] } }, diff --git a/metadata/modules/teqBlazeSalesAgentBidAdapter.json b/metadata/modules/teqBlazeSalesAgentBidAdapter.json new file mode 100644 index 00000000000..2442df240d1 --- /dev/null +++ b/metadata/modules/teqBlazeSalesAgentBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "teqBlazeSalesAgent", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index 4159d72a123..dd50f1b4493 100644 --- a/metadata/modules/tncIdSystem.json +++ b/metadata/modules/tncIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.tncid.app/iab-tcf-device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:50.204Z", + "timestamp": "2026-03-02T14:46:12.515Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index 3a1e57ff65b..e4683b84727 100644 --- a/metadata/modules/topicsFpdModule.json +++ b/metadata/modules/topicsFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/topicsFpdModule.json": { - "timestamp": "2025-11-19T20:51:08.093Z", + "timestamp": "2026-03-02T14:44:46.316Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index 32944cafd7b..719b4868651 100644 --- a/metadata/modules/toponBidAdapter.json +++ b/metadata/modules/toponBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mores.toponad.net/tmp/tpn/toponads_tcf_disclosure.json": { - "timestamp": "2025-11-19T20:51:50.224Z", + "timestamp": "2026-03-02T14:46:12.534Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index e6ad4e4afce..ae8f461ead0 100644 --- a/metadata/modules/tripleliftBidAdapter.json +++ b/metadata/modules/tripleliftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://triplelift.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:50.269Z", + "timestamp": "2026-03-02T14:46:12.661Z", "disclosures": [] } }, diff --git a/metadata/modules/trustxBidAdapter.json b/metadata/modules/trustxBidAdapter.json new file mode 100644 index 00000000000..5f4821f2f56 --- /dev/null +++ b/metadata/modules/trustxBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "trustx", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index ef523655772..825c0388f7a 100644 --- a/metadata/modules/ttdBidAdapter.json +++ b/metadata/modules/ttdBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2025-11-19T20:51:50.328Z", + "timestamp": "2026-03-02T14:46:12.692Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index 41d3ecd154e..fc4095423c5 100644 --- a/metadata/modules/twistDigitalBidAdapter.json +++ b/metadata/modules/twistDigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://twistdigital.net/iab.json": { - "timestamp": "2025-11-19T20:51:50.328Z", + "timestamp": "2026-03-02T14:46:12.692Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index f5b3989c181..dbd497898d7 100644 --- a/metadata/modules/underdogmediaBidAdapter.json +++ b/metadata/modules/underdogmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.underdog.media/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:50.429Z", + "timestamp": "2026-03-02T14:46:12.772Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index 4f7aa901969..0b02483de93 100644 --- a/metadata/modules/undertoneBidAdapter.json +++ b/metadata/modules/undertoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.undertone.com/js/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:50.453Z", + "timestamp": "2026-03-02T14:46:12.790Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index 56adb8f75ad..cc27f390b4d 100644 --- a/metadata/modules/unifiedIdSystem.json +++ b/metadata/modules/unifiedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2025-11-19T20:51:50.469Z", + "timestamp": "2026-03-02T14:46:12.878Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index bb5a9485c71..5d00220a7b8 100644 --- a/metadata/modules/unrulyBidAdapter.json +++ b/metadata/modules/unrulyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:50.470Z", + "timestamp": "2026-03-02T14:46:12.878Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index 1bade253a58..a7288064da5 100644 --- a/metadata/modules/userId.json +++ b/metadata/modules/userId.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/userId-optout.json": { - "timestamp": "2025-11-19T20:51:08.095Z", + "timestamp": "2026-03-02T14:44:46.317Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index bbfc0ae04cb..6e7e629b0ac 100644 --- a/metadata/modules/utiqIdSystem.json +++ b/metadata/modules/utiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:50.470Z", + "timestamp": "2026-03-02T14:46:12.878Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index 1b88f4f1597..8e9e76248b8 100644 --- a/metadata/modules/utiqMtpIdSystem.json +++ b/metadata/modules/utiqMtpIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:50.471Z", + "timestamp": "2026-03-02T14:46:12.879Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index 60290f5246e..05f5921394d 100644 --- a/metadata/modules/validationFpdModule.json +++ b/metadata/modules/validationFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2025-11-19T20:51:08.094Z", + "timestamp": "2026-03-02T14:44:46.316Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index dd16d6190e0..4bcb9939955 100644 --- a/metadata/modules/valuadBidAdapter.json +++ b/metadata/modules/valuadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.valuad.cloud/tcfdevice.json": { - "timestamp": "2025-11-19T20:51:50.471Z", + "timestamp": "2026-03-02T14:46:12.879Z", "disclosures": [] } }, diff --git a/metadata/modules/verbenBidAdapter.json b/metadata/modules/verbenBidAdapter.json new file mode 100644 index 00000000000..47a939ac97a --- /dev/null +++ b/metadata/modules/verbenBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "verben", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index 18fb84cd5f1..d2fee191de4 100644 --- a/metadata/modules/vidazooBidAdapter.json +++ b/metadata/modules/vidazooBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidazoo.com/gdpr-tcf/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:50.772Z", + "timestamp": "2026-03-02T14:46:13.094Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index 0d89d6b88ac..4902540cf3f 100644 --- a/metadata/modules/vidoomyBidAdapter.json +++ b/metadata/modules/vidoomyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidoomy.com/storageurl/devicestoragediscurl.json": { - "timestamp": "2025-11-19T20:51:50.839Z", + "timestamp": "2026-03-02T14:46:13.172Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index 52ef397af3b..c26728473ea 100644 --- a/metadata/modules/viouslyBidAdapter.json +++ b/metadata/modules/viouslyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:51.075Z", + "timestamp": "2026-03-02T14:46:18.010Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 079ec87a9a4..de208d4fa21 100644 --- a/metadata/modules/visxBidAdapter.json +++ b/metadata/modules/visxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.yoc.com/visx/sellers/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:51.077Z", + "timestamp": "2026-03-02T14:46:18.011Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index 66ed24bcdfa..a8a8be09eb6 100644 --- a/metadata/modules/vlybyBidAdapter.json +++ b/metadata/modules/vlybyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vlyby.com/conf/iab/gvl.json": { - "timestamp": "2025-11-19T20:51:51.275Z", + "timestamp": "2026-03-02T14:46:18.324Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index 5f5ff6e0aca..71d1adacb65 100644 --- a/metadata/modules/voxBidAdapter.json +++ b/metadata/modules/voxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:51.299Z", + "timestamp": "2026-03-02T14:46:18.645Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 53853ab1808..492cc4a9c29 100644 --- a/metadata/modules/vrtcalBidAdapter.json +++ b/metadata/modules/vrtcalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vrtcal.com/docs/gdpr-tcf-disclosures.json": { - "timestamp": "2025-11-19T20:51:51.299Z", + "timestamp": "2026-03-02T14:46:18.645Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index ffe75e2969c..9ff99596a87 100644 --- a/metadata/modules/vuukleBidAdapter.json +++ b/metadata/modules/vuukleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vuukle.com/data-privacy/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:51.516Z", + "timestamp": "2026-03-02T14:46:18.662Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 00865923c62..3d5c7c23945 100644 --- a/metadata/modules/weboramaRtdProvider.json +++ b/metadata/modules/weboramaRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://weborama.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:51.832Z", + "timestamp": "2026-03-02T14:46:18.964Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index 60162f3b72a..f6fe924992e 100644 --- a/metadata/modules/welectBidAdapter.json +++ b/metadata/modules/welectBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.welect.de/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:52.087Z", + "timestamp": "2026-03-02T14:46:19.465Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index f4c7bafdc37..214ac134bbe 100644 --- a/metadata/modules/yahooAdsBidAdapter.json +++ b/metadata/modules/yahooAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2025-11-19T20:51:52.466Z", + "timestamp": "2026-03-02T14:46:19.840Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yaleoBidAdapter.json b/metadata/modules/yaleoBidAdapter.json new file mode 100644 index 00000000000..b78e431d9c6 --- /dev/null +++ b/metadata/modules/yaleoBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://audienzz.com/device_storage_disclosure_vendor_783.json": { + "timestamp": "2026-03-02T14:46:19.841Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "yaleo", + "aliasOf": null, + "gvlid": 783, + "disclosureURL": "https://audienzz.com/device_storage_disclosure_vendor_783.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index 3dfd82d0c46..52962311fa8 100644 --- a/metadata/modules/yieldlabBidAdapter.json +++ b/metadata/modules/yieldlabBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.yieldlab.net/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:52.467Z", + "timestamp": "2026-03-02T14:46:19.841Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index 56b0890884e..e0f3e660ae6 100644 --- a/metadata/modules/yieldloveBidAdapter.json +++ b/metadata/modules/yieldloveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn-a.yieldlove.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:52.573Z", + "timestamp": "2026-03-02T14:46:20.279Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index c52a5ce1d5d..ce311353d52 100644 --- a/metadata/modules/yieldmoBidAdapter.json +++ b/metadata/modules/yieldmoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://devicestoragedisclosureurl.yieldmo.com/deviceStorage.json": { - "timestamp": "2025-11-19T20:51:52.596Z", + "timestamp": "2026-03-02T14:46:20.302Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index efb60121dd1..3f9dfb1b424 100644 --- a/metadata/modules/zeotapIdPlusIdSystem.json +++ b/metadata/modules/zeotapIdPlusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spl.zeotap.com/assets/iab-disclosure.json": { - "timestamp": "2025-11-19T20:51:52.678Z", + "timestamp": "2026-03-02T14:46:20.390Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index a6ae367f9b8..f215e1e0463 100644 --- a/metadata/modules/zeta_globalBidAdapter.json +++ b/metadata/modules/zeta_globalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:52.801Z", + "timestamp": "2026-03-02T14:46:20.525Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index 0f39ead30bf..517fd76fb4c 100644 --- a/metadata/modules/zeta_global_sspBidAdapter.json +++ b/metadata/modules/zeta_global_sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2025-11-19T20:51:52.932Z", + "timestamp": "2026-03-02T14:46:20.656Z", "disclosures": [] } }, diff --git a/metadata/overrides.mjs b/metadata/overrides.mjs index bb722cfd4f2..214f778485c 100644 --- a/metadata/overrides.mjs +++ b/metadata/overrides.mjs @@ -17,5 +17,5 @@ export default { relevadRtdProvider: 'RelevadRTDModule', sirdataRtdProvider: 'SirdataRTDModule', fanBidAdapter: 'freedomadnetwork', - uniquestWidgetBidAdapter: 'uniquest_widget' + ringieraxelspringerBidAdapter: 'das' } diff --git a/modules/.submodules.json b/modules/.submodules.json index 791f7ed822b..c6b0db32e78 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -32,6 +32,7 @@ "kinessoIdSystem", "liveIntentIdSystem", "lmpIdSystem", + "locIdSystem", "lockrAIMIdSystem", "lotamePanoramaIdSystem", "merkleIdSystem", @@ -111,11 +112,13 @@ "mobianRtdProvider", "neuwoRtdProvider", "nodalsAiRtdProvider", + "oftmediaRtdProvider", "oneKeyRtdProvider", "optableRtdProvider", "optimeraRtdProvider", "overtoneRtdProvider", "oxxionRtdProvider", + "panxoRtdProvider", "permutiveRtdProvider", "pubmaticRtdProvider", "pubxaiRtdProvider", @@ -146,4 +149,4 @@ "topLevelPaapi" ] } -} \ No newline at end of file +} diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js index 44870c97849..5c07511a280 100644 --- a/modules/51DegreesRtdProvider.js +++ b/modules/51DegreesRtdProvider.js @@ -8,6 +8,7 @@ import { mergeDeep, prefixLog, } from '../src/utils.js'; +import {getDevicePixelRatio} from '../libraries/devicePixelRatio/devicePixelRatio.js'; const MODULE_NAME = '51Degrees'; export const LOG_PREFIX = `[${MODULE_NAME} RTD Submodule]:`; @@ -126,7 +127,7 @@ export const get51DegreesJSURL = (pathData, win) => { ); deepSetNotEmptyValue(qs, '51D_ScreenPixelsHeight', _window?.screen?.height); deepSetNotEmptyValue(qs, '51D_ScreenPixelsWidth', _window?.screen?.width); - deepSetNotEmptyValue(qs, '51D_PixelRatio', _window?.devicePixelRatio); + deepSetNotEmptyValue(qs, '51D_PixelRatio', getDevicePixelRatio(_window)); const _qs = formatQS(qs); const _qsString = _qs ? `${queryPrefix}${_qs}` : ''; @@ -227,6 +228,7 @@ export const convert51DegreesDataToOrtb2 = (data51) => { * @param {number} [device.screenpixelsphysicalwidth] Screen physical width in pixels * @param {number} [device.pixelratio] Pixel ratio * @param {number} [device.screeninchesheight] Screen height in inches + * @param {string} [device.thirdpartycookiesenabled] Third-party cookies enabled * * @returns {Object} Enriched ORTB2 object */ @@ -261,7 +263,12 @@ export const convert51DegreesDeviceToOrtb2 = (device) => { deepSetNotEmptyValue(ortb2Device, 'w', device.screenpixelsphysicalwidth || device.screenpixelswidth); deepSetNotEmptyValue(ortb2Device, 'pxratio', device.pixelratio); deepSetNotEmptyValue(ortb2Device, 'ppi', devicePhysicalPPI || devicePPI); + // kept for backward compatibility deepSetNotEmptyValue(ortb2Device, 'ext.fiftyonedegrees_deviceId', device.deviceid); + deepSetNotEmptyValue(ortb2Device, 'ext.fod.deviceId', device.deviceid); + if (['True', 'False'].includes(device.thirdpartycookiesenabled)) { + deepSetValue(ortb2Device, 'ext.fod.tpc', device.thirdpartycookiesenabled === 'True' ? 1 : 0); + } return {device: ortb2Device}; } diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md index 76fa73803c9..db4b930c25e 100644 --- a/modules/51DegreesRtdProvider.md +++ b/modules/51DegreesRtdProvider.md @@ -8,13 +8,17 @@ ## Description -The 51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). +51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). -The 51Degrees module sets the following fields of the device object: `devicetype`, `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio`. Interested bidder adapters may use these fields as needed. In addition, the module sets `device.ext.fiftyonedegrees_deviceId` to a permanent device ID, which can be rapidly looked up in on-premise data, exposing over 250 properties, including device age, chipset, codec support, price, operating system and app/browser versions, age, and embedded features. +51Degrees module sets the following fields of the device object: `devicetype`, `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio`. Interested bidder adapters may use these fields as needed. + +The module also adds a `device.ext.fod` extension object (fod == fifty one degrees) and sets `device.ext.fod.deviceId` to a permanent device ID, which can be rapidly looked up in on-premise data, exposing over 250 properties, including device age, chipset, codec support, price, operating system and app/browser versions, age, and embedded features. + +It also sets `device.ext.fod.tpc` key to a binary value to indicate whether third-party cookies are enabled in the browser (1 if enabled, 0 if disabled). The module supports on-premise and cloud device detection services, with free options for both. -A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/HNZ75HT1). This is the simplest approach to trial the module. +A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/7bL8jDGz). 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). @@ -36,7 +40,7 @@ gulp build --modules=rtdModule,51DegreesRtdProvider,appnexusBidAdapter,... #### 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: +In order to use the module, please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/7bL8jDGz) - choose the following properties: * DeviceId * DeviceType @@ -52,6 +56,7 @@ In order to use the module, please first obtain a Resource Key using the [Config * ScreenInchesHeight * ScreenInchesWidth * PixelRatio +* ThirdPartyCookiesEnabled The Cloud API is **free** to integrate and use. To increase limits, please check [51Degrees pricing](https://51degrees.com/pricing). @@ -106,7 +111,7 @@ pbjs.setConfig({ waitForIt: true, // should be true, otherwise the auctionDelay will be ignored params: { resourceKey: '', - // Get your resource key from https://configure.51degrees.com/HNZ75HT1 + // Get your resource key from https://configure.51degrees.com/7bL8jDGz // alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen endpoint // onPremiseJSUrl: 'https://localhost/51Degrees.core.js' }, diff --git a/modules/aceexBidAdapter.js b/modules/aceexBidAdapter.js new file mode 100644 index 00000000000..71c3ac070dc --- /dev/null +++ b/modules/aceexBidAdapter.js @@ -0,0 +1,97 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { + buildRequestsBase, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.js'; + +import { deepAccess } from '../src/utils.js'; + +const BIDDER_CODE = 'aceex'; +const GVLID = 1387; +const AD_REQUEST_URL = 'https://bl-us.aceex.io/?secret_key=prebidjs'; + +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + placement.trafficType = placement.adFormat; + placement.publisherId = bid.params.publisherId; + placement.internalKey = bid.params.internalKey; +}; + +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return !!(bid.bidId && bid.params?.publisherId && bid.params?.trafficType); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const base = buildRequestsBase({ adUrl: AD_REQUEST_URL, validBidRequests, bidderRequest, placementProcessingFunction }); + + base.data.cat = deepAccess(bidderRequest, 'ortb2.cat'); + base.data.keywords = deepAccess(bidderRequest, 'ortb2.keywords'); + base.data.badv = deepAccess(bidderRequest, 'ortb2.badv'); + base.data.wseat = deepAccess(bidderRequest, 'ortb2.wseat'); + base.data.bseat = deepAccess(bidderRequest, 'ortb2.bseat'); + + return base; + }, + + interpretResponse: (serverResponse, bidRequest) => { + if (!serverResponse || !serverResponse.body || !Array.isArray(serverResponse.body.seatbid)) return []; + + const repackedBids = []; + + serverResponse.body.seatbid.forEach(seatbidItem => { + seatbidItem.bid.forEach((bid) => { + const originalPlacement = bidRequest.data.placements?.find(pl => pl.bidId === bid.id); + + const repackedBid = { + cpm: bid.price, + creativeId: bid.crid, + currency: 'USD', + dealId: bid.dealid, + height: bid.h, + width: bid.w, + mediaType: originalPlacement.adFormat, + netRevenue: true, + requestId: bid.id, + ttl: 1200, + meta: { + advertiserDomains: bid.adomain + }, + }; + + switch (originalPlacement.adFormat) { + case 'video': + repackedBid.vastXml = bid.adm; + break; + + case 'banner': + repackedBid.ad = bid.adm; + break; + + case 'native': + const nativeResponse = JSON.parse(bid.adm).native; + + const { assets, imptrackers, link } = nativeResponse; + repackedBid.native = { + ortb: { assets, imptrackers, link }, + }; + break; + + default: break; + }; + + repackedBids.push(repackedBid); + }) + }); + + return repackedBids; + }, +}; + +registerBidder(spec); diff --git a/modules/aceexBidAdapter.md b/modules/aceexBidAdapter.md new file mode 100644 index 00000000000..6efa00acd47 --- /dev/null +++ b/modules/aceexBidAdapter.md @@ -0,0 +1,67 @@ +# Overview + +``` +Module Name: Aceex Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@aceex.io +``` + +# Description + +Module that connects Prebid.JS publishers to Aceex ad-exchange + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `publisherId` | required | Publisher ID on platform | 219 | +| `trafficType` | required | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | +| `internalKey` | required | Publisher hash on platform | "j1opp02hsma8119" | +| `bidfloor` | required | Bidfloor | 0.1 | + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'aceex', + params: { + publisherId: 219, + internalKey: 'j1opp02hsma8119', + trafficType: 'banner', + bidfloor: 0.2 + } + } + ] + }, + // Will return test vast video + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'aceex', + params: { + publisherId: 219, + internalKey: 'j1opp02hsma8119', + trafficType: 'video', + bidfloor: 1.1 + } + } + ] + } + ]; +``` diff --git a/modules/adbroBidAdapter.js b/modules/adbroBidAdapter.js index 2c0c86f63f7..bba051933c7 100644 --- a/modules/adbroBidAdapter.js +++ b/modules/adbroBidAdapter.js @@ -66,6 +66,9 @@ export const spec = { result.push({ method: 'POST', url: ENDPOINT_URL + '?placementId=' + id, + options: { + endpointCompression: true, + }, data }); }); diff --git a/modules/adclusterBidAdapter.js b/modules/adclusterBidAdapter.js new file mode 100644 index 00000000000..b8b1f248582 --- /dev/null +++ b/modules/adclusterBidAdapter.js @@ -0,0 +1,183 @@ +import { registerBidder } from "../src/adapters/bidderFactory.js"; +import { BANNER, VIDEO } from "../src/mediaTypes.js"; + +const BIDDER_CODE = "adcluster"; +const ENDPOINT = "https://core.adcluster.com.tr/bid"; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bid) { + return !!bid?.params?.unitId; + }, + + buildRequests(validBidRequests, bidderRequest) { + const _auctionId = bidderRequest.auctionId || ""; + const payload = { + bidderCode: bidderRequest.bidderCode, + auctionId: _auctionId, + bidderRequestId: bidderRequest.bidderRequestId, + bids: validBidRequests.map((b) => buildImp(b)), + auctionStart: bidderRequest.auctionStart, + timeout: bidderRequest.timeout, + start: bidderRequest.start, + regs: { ext: {} }, + user: { ext: {} }, + source: { ext: {} }, + }; + + // privacy + if (bidderRequest?.gdprConsent) { + payload.regs = payload.regs || { ext: {} }; + payload.regs.ext = payload.regs.ext || {}; + payload.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + payload.user.ext.consent = bidderRequest.gdprConsent.consentString || ""; + } + if (bidderRequest?.uspConsent) { + payload.regs = payload.regs || { ext: {} }; + payload.regs.ext.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest?.ortb2?.regs?.gpp) { + payload.regs = payload.regs || { ext: {} }; + payload.regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + payload.regs.ext.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + if (validBidRequests[0]?.userIdAsEids) { + payload.user.ext.eids = validBidRequests[0].userIdAsEids; + } + if (validBidRequests[0]?.ortb2?.source?.ext?.schain) { + payload.source.ext.schain = validBidRequests[0].ortb2.source.ext.schain; + } + + return { + method: "POST", + url: ENDPOINT, + data: payload, + options: { contentType: "text/plain" }, + }; + }, + + interpretResponse(serverResponse) { + const body = serverResponse?.body; + if (!body || !Array.isArray(body)) return []; + const bids = []; + + body.forEach((b) => { + const mediaType = detectMediaType(b); + const bid = { + requestId: b.requestId, + cpm: b.cpm, + currency: b.currency, + width: b.width, + height: b.height, + creativeId: b.creativeId, + ttl: b.ttl, + netRevenue: b.netRevenue, + meta: { + advertiserDomains: b.meta?.advertiserDomains || [], + }, + mediaType, + }; + + if (mediaType === BANNER) { + bid.ad = b.ad; + } + if (mediaType === VIDEO) { + bid.vastUrl = b.ad; + } + bids.push(bid); + }); + + return bids; + }, +}; + +/* ---------- helpers ---------- */ + +function buildImp(bid) { + const _transactionId = bid.transactionId || ""; + const _adUnitId = bid.adUnitId || ""; + const _auctionId = bid.auctionId || ""; + const imp = { + params: { + unitId: bid.params.unitId, + }, + bidId: bid.bidId, + bidderRequestId: bid.bidderRequestId, + transactionId: _transactionId, + adUnitId: _adUnitId, + auctionId: _auctionId, + ext: { + floors: getFloorsAny(bid), + }, + }; + + if (bid.params && bid.params.previewMediaId) { + imp.params.previewMediaId = bid.params.previewMediaId; + } + + const mt = bid.mediaTypes || {}; + + // BANNER + if (mt.banner?.sizes?.length) { + imp.width = mt.banner.sizes[0] && mt.banner.sizes[0][0]; + imp.height = mt.banner.sizes[0] && mt.banner.sizes[0][1]; + } + if (mt.video) { + const v = mt.video; + const playerSize = toSizeArray(v.playerSize); + const [vw, vh] = playerSize?.[0] || []; + imp.width = vw; + imp.height = vh; + imp.video = { + minduration: v.minduration || 1, + maxduration: v.maxduration || 120, + ext: { + context: v.context || "instream", + floor: getFloors(bid, "video", playerSize?.[0]), + }, + }; + } + + return imp; +} + +function toSizeArray(s) { + if (!s) return null; + // playerSize can be [w,h] or [[w,h], [w2,h2]] + return Array.isArray(s[0]) ? s : [s]; +} + +function getFloors(bid, mediaType = "banner", size) { + try { + if (!bid.getFloor) return null; + // size can be [w,h] or '*' + const sz = Array.isArray(size) ? size : "*"; + const res = bid.getFloor({ mediaType, size: sz }); + return res && typeof res.floor === "number" ? res.floor : null; + } catch { + return null; + } +} + +function detectMediaType(bid) { + if (bid.mediaType === "video") return VIDEO; + else return BANNER; +} + +function getFloorsAny(bid) { + // Try to collect floors per type + const out = {}; + const mt = bid.mediaTypes || {}; + if (mt.banner) { + out.banner = getFloors(bid, "banner", "*"); + } + if (mt.video) { + const ps = toSizeArray(mt.video.playerSize); + out.video = getFloors(bid, "video", (ps && ps[0]) || "*"); + } + return out; +} + +registerBidder(spec); diff --git a/modules/adclusterBidAdapter.md b/modules/adclusterBidAdapter.md new file mode 100644 index 00000000000..59300e2c857 --- /dev/null +++ b/modules/adclusterBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +**Module Name**: Adcluster Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: dev@adcluster.com.tr + +# Description + +Prebid.js bidder adapter module for connecting to Adcluster. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'adcluster-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'adcluster', + params: { + unitId: '42d1f525-5792-47a6-846d-1825e53c97d6', + previewMediaId: "b4dbc48c-0b90-4628-bc55-f46322b89b63", + }, + }] + }, + { + code: 'adcluster-video', + mediaTypes: { + video: { + playerSize: [[640, 480]], + } + }, + bids: [{ + bidder: 'adcluster', + params: { + unitId: "37dd91b2-049d-4027-94b9-d63760fc10d3", + previewMediaId: "133b7dc9-bb6e-4ab2-8f95-b796cf19f27e", + }, + }] + } +]; +``` diff --git a/modules/adipoloBidAdapter.js b/modules/adipoloBidAdapter.js index ace9fc42c86..51c2a97a1f0 100644 --- a/modules/adipoloBidAdapter.js +++ b/modules/adipoloBidAdapter.js @@ -1,6 +1,7 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; +import { getTimeZone } from '../libraries/timezone/timezone.js'; const BIDDER_CODE = 'adipolo'; const GVL_ID = 1456; @@ -12,8 +13,7 @@ function getSubdomain() { }; try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; + const region = getTimeZone().split('/')[0]; return regionMap[region] || regionMap.America; } catch (err) { return regionMap.America; diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 4bf011cc12c..81fa3501ea0 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -107,7 +107,9 @@ export const spec = { {code: 'smartyexchange'}, {code: 'infinety'}, {code: 'qohere'}, - {code: 'blutonic'} + {code: 'blutonic'}, + {code: 'appmonsta', gvlid: 1283}, + {code: 'intlscoop'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index d3f28af5f2c..d83e7ed5ae9 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -28,6 +28,7 @@ export const spec = { { code: 'monetixads', gvlid: 1281 }, { code: 'netaddiction', gvlid: 1281 }, { code: 'adt', gvlid: 779 }, + { code: 'adrubi', gvlid: 779 }, { code: 'yobee', gvlid: 1281 } ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -364,8 +365,11 @@ function buildRequestObject(bid) { reqObj.mediatype = bid.mediaTypes.native; } + reqObj.ext = reqObj.ext || {}; + if (deepAccess(bid, 'ortb2Imp.ext')) { - reqObj.ext = bid.ortb2Imp.ext; + Object.assign(reqObj.ext, bid.ortb2Imp.ext); + reqObj.ext.ortb2Imp = bid.ortb2Imp; } reqObj.id = getBidIdParameter('bidId', bid); diff --git a/modules/adnimationBidAdapter.js b/modules/adnimationBidAdapter.js new file mode 100644 index 00000000000..21ac9090f38 --- /dev/null +++ b/modules/adnimationBidAdapter.js @@ -0,0 +1,47 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { + isBidRequestValid, + onBidWon, + createUserSyncGetter, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; + +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'adnimation'; +const BIDDER_VERSION = '1.0.0'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.adnimation.com`; +} + +function createUniqueRequestData(hashUrl, bid) { + const {auctionId, transactionId} = bid; + return { + auctionId, + transactionId + }; +} + +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.adnimation.com/api/sync/iframe', + imageSyncUrl: 'https://sync.adnimation.com/api/sync/image' +}); + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, +}; + +registerBidder(spec); diff --git a/modules/adnimationBidAdapter.md b/modules/adnimationBidAdapter.md new file mode 100644 index 00000000000..df62967bd8d --- /dev/null +++ b/modules/adnimationBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +**Module Name:** Adnimation Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** prebid@adnimation.com + +# Description + +Module that connects to Adnimation's demand sources. + +# Test Parameters + +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'adnimation', + params: { + cId: '562524b21b1c1f08117667f9', + pId: '59ac17c192832d0016683fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/adoceanBidAdapter.js b/modules/adoceanBidAdapter.js index 6f6548a1ba8..410b3c92338 100644 --- a/modules/adoceanBidAdapter.js +++ b/modules/adoceanBidAdapter.js @@ -3,6 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'adocean'; +const GVLID = 328; const URL_SAFE_FIELDS = { slaves: true }; @@ -117,6 +118,7 @@ function interpretResponse(placementResponse, bidRequest, bids) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index b0770d3e45e..36a7eee206c 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -1,6 +1,13 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {buildUrl, logInfo, logMessage, parseSizesInput, triggerPixel} from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { + buildUrl, + logInfo, + logMessage, + parseSizesInput, + triggerPixel, + deepSetValue +} from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -25,13 +32,18 @@ const ADQUERY_TTL = 360; export const spec = { code: ADQUERY_BIDDER_CODE, gvlid: ADQUERY_GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * @param {object} bid * @return {boolean} */ isBidRequestValid: (bid) => { + const video = bid.mediaTypes && bid.mediaTypes.video; + if (video) { + return !!(video.playerSize && video.context === 'outstream');// Focus on outstream + } + return !!(bid && bid.params && bid.params.placementId && bid.mediaTypes.banner.sizes) }, @@ -47,19 +59,33 @@ export const spec = { protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, hostname: ADQUERY_BIDDER_DOMAIN, pathname: '/prebid/bid', - // search: params }); for (let i = 0, len = bidRequests.length; i < len; i++) { + const bid = bidRequests[i]; + const isVideo = bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context === 'outstream'; + + let requestUrl = adqueryRequestUrl; + + if (isVideo) { + requestUrl = buildUrl({ + protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, + hostname: ADQUERY_BIDDER_DOMAIN, + pathname: '/openrtb2/auction2', + }); + } + const request = { method: 'POST', - url: adqueryRequestUrl, // ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUERY_BIDDER_DOMAIN + '/prebid/bid', - data: buildRequest(bidRequests[i], bidderRequest), + url: requestUrl, + data: buildRequest(bid, bidderRequest, isVideo), options: { withCredentials: false, crossOrigin: true - } + }, + bidId: bid.bidId }; + requests.push(request); } return requests; @@ -71,14 +97,47 @@ export const spec = { * @return {Bid[]} */ interpretResponse: (response, request) => { - logMessage(request); - logMessage(response); + const bidResponses = []; + + if (response?.body?.seatbid) { + response.body.seatbid.forEach(seat => { + seat.bid.forEach(bid => { + logMessage('bidObj', bid); + + const bidResponse = { + requestId: bid.impid, + mediaType: 'video', + cpm: bid.price, + currency: response.body.cur || 'USD', + ttl: 3600, // video żyje dłużej + creativeId: bid.crid || bid.id, + netRevenue: true, + dealId: bid.dealid || undefined, + nurl: bid.nurl || undefined, + + // VAST – priority: inline XML > admurl > nurl as a wrapper + vastXml: bid.adm || null, + vastUrl: bid.admurl || null, + + width: bid.w || 640, + height: bid.h || 360, + + meta: { + advertiserDomains: bid.adomain && bid.adomain.length ? bid.adomain : [], + networkName: seat.seat || undefined, + mediaType: 'video' + } + }; + + bidResponses.push(bidResponse); + }); + }); + } const res = response && response.body && response.body.data; - const bidResponses = []; if (!res) { - return []; + return bidResponses; } const bidResponse = { @@ -134,6 +193,12 @@ export const spec = { */ onBidWon: (bid) => { logInfo('onBidWon', bid); + + if (bid.nurl) { + triggerPixel(bid.nurl) + return + } + const copyOfBid = { ...bid } delete copyOfBid.ad const shortBidString = JSON.stringify(copyOfBid); @@ -222,10 +287,7 @@ export const spec = { } }; -function buildRequest(validBidRequests, bidderRequest) { - const bid = validBidRequests; - logInfo('buildRequest: ', bid); - +function buildRequest(bid, bidderRequest, isVideo = false) { let userId = null; if (window.qid) { userId = window.qid; @@ -235,6 +297,10 @@ function buildRequest(validBidRequests, bidderRequest) { userId = bid.userId.qid } + if (!userId) { + userId = bid.ortb2?.user.ext.eids.find(eid => eid.source === "adquery.io")?.uids[0]?.id; + } + if (!userId) { // onetime User ID const ramdomValues = Array.from(window.crypto.getRandomValues(new Uint32Array(4))); @@ -248,6 +314,41 @@ function buildRequest(validBidRequests, bidderRequest) { pageUrl = bidderRequest.refererInfo.page || ''; } + if (isVideo) { + let baseRequest = bid.ortb2 + let videoRequest = { + ...baseRequest, + imp: [{ + id: bid.bidId, + video: bid.ortb2Imp?.video || {}, + }] + } + + deepSetValue(videoRequest, 'site.ext.bidder', bid.params); + videoRequest.id = bid.bidId + + let currency = bid?.ortb2?.ext?.prebid?.adServerCurrency || "PLN"; + videoRequest.cur = [ currency ] + + let floorInfo; + if (typeof bid.getFloor === 'function') { + floorInfo = bid.getFloor({ + currency: currency, + mediaType: "video", + size: "*" + }); + } + const bidfloor = floorInfo?.floor; + const bidfloorcur = floorInfo?.currency; + + if (bidfloor && bidfloorcur) { + videoRequest.imp[0].video.bidfloor = bidfloor + videoRequest.imp[0].video.bidfloorcur = bidfloorcur + } + + return videoRequest + } + return { v: '$prebid.version$', placementCode: bid.params.placementId, diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 2c4278b9fb8..d8759bcbda6 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -400,7 +400,7 @@ function _createImpressionObject(bid, conf) { } } else { // mediaTypes is not present, so this is a banner only impression - // this part of code is required for older testcases with no 'mediaTypes' to run succesfully. + // this part of code is required for older testcases with no 'mediaTypes' to run successfully. bannerObj = { pos: 0, w: bid.params.width, diff --git a/modules/adverxoBidAdapter.js b/modules/adverxoBidAdapter.js index 20ff4acb939..a4e77ee9f30 100644 --- a/modules/adverxoBidAdapter.js +++ b/modules/adverxoBidAdapter.js @@ -21,14 +21,16 @@ const BIDDER_CODE = 'adverxo'; const ALIASES = [ {code: 'adport', skipPbsAliasing: true}, {code: 'bidsmind', skipPbsAliasing: true}, - {code: 'harrenmedia', skipPbsAliasing: true} + {code: 'harrenmedia', skipPbsAliasing: true}, + {code: 'alchemyx', skipPbsAliasing: true} ]; const AUCTION_URLS = { adverxo: 'js.pbsadverxo.com', adport: 'ayuetina.com', bidsmind: 'arcantila.com', - harrenmedia: 'harrenmediaprebid.com' + harrenmedia: 'harrenmediaprebid.com', + alchemyx: 'alchemyx.one' }; const ENDPOINT_URL_AD_UNIT_PLACEHOLDER = '{AD_UNIT}'; diff --git a/modules/allegroBidAdapter.js b/modules/allegroBidAdapter.js new file mode 100644 index 00000000000..3c42c9f1e60 --- /dev/null +++ b/modules/allegroBidAdapter.js @@ -0,0 +1,257 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {config} from '../src/config.js'; +import {triggerPixel, logInfo, logError} from '../src/utils.js'; + +const BIDDER_CODE = 'allegro'; +const BIDDER_URL = 'https://prebid.rtb.allegrogroup.com/v1/rtb/prebid/bid'; +const GVLID = 1493; + +/** + * Traverses an OpenRTB bid request object and moves any ext objects into + * DoubleClick (Google) style bracketed keys (e.g. ext -> [com.google.doubleclick.site]). + * Also normalizes certain integer flags into booleans (e.g. gdpr: 1 -> true). + * This mutates the provided request object in-place. + * + * @param request OpenRTB bid request being prepared for sending. + */ +function convertExtensionFields(request) { + if (request.imp) { + request.imp.forEach(imp => { + if (imp.banner?.ext) { + moveExt(imp.banner, '[com.google.doubleclick.banner_ext]') + } + if (imp.ext) { + moveExt(imp, '[com.google.doubleclick.imp]') + } + }); + } + + if (request.app?.ext) { + moveExt(request.app, '[com.google.doubleclick.app]') + } + + if (request.site?.ext) { + moveExt(request.site, '[com.google.doubleclick.site]') + } + + if (request.site?.publisher?.ext) { + moveExt(request.site.publisher, '[com.google.doubleclick.publisher]') + } + + if (request.user?.ext) { + moveExt(request.user, '[com.google.doubleclick.user]') + } + + if (request.user?.data) { + request.user.data.forEach(data => { + if (data.ext) { + moveExt(data, '[com.google.doubleclick.data]') + } + }); + } + + if (request.device?.ext) { + moveExt(request.device, '[com.google.doubleclick.device]') + } + + if (request.device?.geo?.ext) { + moveExt(request.device.geo, '[com.google.doubleclick.geo]') + } + + if (request.regs?.ext) { + if (request.regs?.ext?.gdpr !== undefined) { + request.regs.ext.gdpr = request.regs.ext.gdpr === 1; + } + + moveExt(request.regs, '[com.google.doubleclick.regs]') + } + + if (request.source?.ext) { + moveExt(request.source, '[com.google.doubleclick.source]') + } + + if (request.ext) { + moveExt(request, '[com.google.doubleclick.bid_request]') + } +} + +/** + * Moves an `ext` field from a given object to a new bracketed key, cloning its contents. + * If object or ext is missing nothing is done. + * + * @param obj The object potentially containing `ext`. + * @param {string} newKey The destination key name (e.g. '[com.google.doubleclick.site]'). + */ +function moveExt(obj, newKey) { + if (!obj || !obj.ext) { + return; + } + const extCopy = {...obj.ext}; + delete obj.ext; + obj[newKey] = extCopy; +} + +/** + * Custom ORTB converter configuration adjusting request/imp level boolean coercions + * and migrating extension fields depending on config. Provides `toORTB` and `fromORTB` + * helpers used in buildRequests / interpretResponse. + */ +const converter = ortbConverter({ + context: { + mediaType: BANNER, + ttl: 360, + netRevenue: true + }, + + /** + * Builds and post-processes a single impression object, coercing integer flags to booleans. + * + * @param {Function} buildImp Base builder provided by ortbConverter. + * @param bidRequest Individual bid request from Prebid. + * @param context Shared converter context. + * @returns {Object} ORTB impression object. + */ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + if (imp?.banner?.topframe !== undefined) { + imp.banner.topframe = imp.banner.topframe === 1; + } + if (imp?.secure !== undefined) { + imp.secure = imp.secure === 1; + } + return imp; + }, + + /** + * Builds the full ORTB request and normalizes integer flags. Optionally migrates ext fields + * into Google style bracketed keys unless disabled via `allegro.convertExtensionFields` config. + * + * @param {Function} buildRequest Base builder provided by ortbConverter. + * @param {Object[]} imps Array of impression objects. + * @param bidderRequest Prebid bidderRequest (contains refererInfo, gdpr, etc.). + * @param context Shared converter context. + * @returns {Object} Mutated ORTB request object ready to serialize. + */ + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + if (request?.device?.dnt !== undefined) { + request.device.dnt = request.device.dnt === 1; + } + + if (request?.device?.sua?.mobile !== undefined) { + request.device.sua.mobile = request.device.sua.mobile === 1; + } + + if (request?.test !== undefined) { + request.test = request.test === 1; + } + + // by default, we convert extension fields unless the config explicitly disables it + const convertExtConfig = config.getConfig('allegro.convertExtensionFields'); + if (convertExtConfig === undefined || convertExtConfig === true) { + convertExtensionFields(request); + } + + if (request?.source?.schain && !isSchainValid(request.source.schain)) { + delete request.source.schain; + } + + return request; + } +}) + +/** + * Validates supply chain object structure + * @param schain - Supply chain object + * @return {boolean} True if valid, false otherwise + */ +function isSchainValid(schain) { + try { + if (!schain || !schain.nodes || !Array.isArray(schain.nodes)) { + return false; + } + const requiredFields = ['asi', 'sid', 'hp']; + return schain.nodes.every(node => + requiredFields.every(field => node.hasOwnProperty(field)) + ); + } catch (error) { + logError('Allegro: Error validating schain:', error); + return false; + } +} + +/** + * Allegro Bid Adapter specification object consumed by Prebid core. + */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + gvlid: GVLID, + + /** + * Validates an incoming bid object. + * + * @param bid Prebid bid request params. + * @returns {boolean} True if bid is considered valid. + */ + isBidRequestValid: function (bid) { + return !!(bid); + }, + + /** + * Generates the network request payload for the adapter. + * + * @param bidRequests List of valid bid requests. + * @param bidderRequest Aggregated bidder request data (gdpr, usp, refererInfo, etc.). + * @returns Request details for Prebid to send. + */ + buildRequests: function (bidRequests, bidderRequest) { + const url = config.getConfig('allegro.bidderUrl') || BIDDER_URL; + + return { + method: 'POST', + url: url, + data: converter.toORTB({bidderRequest, bidRequests}), + options: { + contentType: 'text/plain' + }, + } + }, + + /** + * Parses the server response into Prebid bid objects. + * + * @param response Server response wrapper from Prebid XHR (expects `body`). + * @param request Original request object passed to server (contains `data`). + */ + interpretResponse: function (response, request) { + if (!response.body) return; + return converter.fromORTB({response: response.body, request: request.data}).bids; + }, + + /** + * Fires impression tracking pixel when the bid wins if enabled by config. + * + * @param bid The winning bid object. + */ + onBidWon: function (bid) { + const triggerImpressionPixel = config.getConfig('allegro.triggerImpressionPixel'); + + if (triggerImpressionPixel && bid.burl) { + triggerPixel(bid.burl); + } + + if (config.getConfig('debug')) { + logInfo('bid won', bid); + } + } + +} + +registerBidder(spec); diff --git a/modules/allegroBidAdapter.md b/modules/allegroBidAdapter.md new file mode 100644 index 00000000000..75ee1720bb5 --- /dev/null +++ b/modules/allegroBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +**Module Name**: Allegro Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: the-bidders@allegro.com +**GVLID**: 1493 + +# Description + +Connects to Allegro's demand sources for banner advertising. This adapter uses the OpenRTB 2.5 protocol with support for extension field conversion to Google DoubleClick proto format. + +# Supported Media Types + +- Banner +- Native +- Video + +# Configuration + +The Allegro adapter supports the following configuration options: + +## Global Configuration Parameters + +| Name | Scope | Type | Description | Default | +|----------------------------------|----------|---------|-----------------------------------------------------------------------------|---------------------------------------------------------| +| `allegro.bidderUrl` | optional | String | Custom bidder endpoint URL | `https://prebid.rtb.allegrogroup.com/v1/rtb/prebid/bid` | +| `allegro.convertExtensionFields` | optional | Boolean | Enable/disable conversion of OpenRTB extension fields to DoubleClick format | `true` | +| `allegro.triggerImpressionPixel` | optional | Boolean | Enable/disable triggering impression tracking pixels on bid won event | `false` | + +## Configuration example + +```javascript +pbjs.setConfig({ + allegro: { + triggerImpressionPixel: true + } +}); +``` + +# AdUnit Configuration Example + +## Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90], + [300, 600] + ] + } + }, + bids: [{ + bidder: 'allegro' + }] +}]; +``` + +# Features +## Impression Tracking + +When `allegro.triggerImpressionPixel` is enabled, the adapter will automatically fire the provided `burl` (billing/impression) tracking URL when a bid wins. + +# Technical Details + +- **Protocol**: OpenRTB 2.5 +- **TTL**: 360 seconds +- **Net Revenue**: true +- **Content Type**: text/plain + diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index b1b22ec19f9..ef89ecdd40f 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -251,7 +251,7 @@ function getSyncSettings() { const all = isSyncEnabled(syncConfig.filterSettings, 'all'); if (all) { - settings.t = SYNC_IMAGE & SYNC_IFRAME; + settings.t = SYNC_IMAGE | SYNC_IFRAME; return settings; } diff --git a/modules/apesterBidAdapter.js b/modules/apesterBidAdapter.js new file mode 100644 index 00000000000..589b1b5210f --- /dev/null +++ b/modules/apesterBidAdapter.js @@ -0,0 +1,49 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { + isBidRequestValid, + onBidWon, + createUserSyncGetter, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; + +const DEFAULT_SUB_DOMAIN = 'bidder'; +const BIDDER_CODE = 'apester'; +const BIDDER_VERSION = '1.0.0'; +const GVLID = 354; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.apester.com`; +} + +function createUniqueRequestData(hashUrl, bid) { + const {auctionId, transactionId} = bid; + return { + auctionId, + transactionId + }; +} + +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.apester.com/api/sync/iframe', + imageSyncUrl: 'https://sync.apester.com/api/sync/image' +}); + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, +}; + +registerBidder(spec); diff --git a/modules/apesterBidAdapter.md b/modules/apesterBidAdapter.md new file mode 100644 index 00000000000..c43707d8a28 --- /dev/null +++ b/modules/apesterBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +**Module Name:** Apester Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** roni.katz@apester.com + +# Description + +Module that connects to Apester's demand sources. + +# Test Parameters + +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'apester', + params: { + cId: '562524b21b1c1f08117667f9', + pId: '59ac17c192832d0016683fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/apsBidAdapter.js b/modules/apsBidAdapter.js new file mode 100644 index 00000000000..732737d32b7 --- /dev/null +++ b/modules/apsBidAdapter.js @@ -0,0 +1,367 @@ +import { isStr, isNumber, logWarn, logError } 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 { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.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 + */ + +const GVLID = 793; +export const ADAPTER_VERSION = '2.0.0'; +const BIDDER_CODE = 'aps'; +const AAX_ENDPOINT = 'https://web.ads.aps.amazon-adsystem.com/e/pb/bid'; +const DEFAULT_PREBID_CREATIVE_JS_URL = + 'https://client.aps.amazon-adsystem.com/prebid-creative.js'; + +/** + * Records an event by pushing a CustomEvent onto a global queue. + * Creates an account-specific store on window._aps if needed. + * Automatically prefixes eventName with 'prebidAdapter/' if not already prefixed. + * Automatically appends '/didTrigger' if there is no third part provided in the event name. + * + * @param {string} eventName - The name of the event to record + * @param {object} data - Event data object, typically containing an 'error' property + */ +function record(eventName, data) { + // Check if telemetry is enabled + if (config.readConfig('aps.telemetry') === false) { + return; + } + + // Automatically prefix eventName with 'prebidAdapter/' if not already prefixed + const prefixedEventName = eventName.startsWith('prebidAdapter/') + ? eventName + : `prebidAdapter/${eventName}`; + + // Automatically append 'didTrigger' if there is no third part provided in the event name + const parts = prefixedEventName.split('/'); + const finalEventName = + parts.length < 3 ? `${prefixedEventName}/didTrigger` : prefixedEventName; + + const accountID = config.readConfig('aps.accountID'); + if (!accountID) { + return; + } + + window._aps = window._aps || new Map(); + if (!window._aps.has(accountID)) { + window._aps.set(accountID, { + queue: [], + store: new Map(), + }); + } + + // Ensure analytics key exists unless error key is present + const detailData = { ...data }; + if (!detailData.error) { + detailData.analytics = detailData.analytics || {}; + } + + window._aps.get(accountID).queue.push( + new CustomEvent(finalEventName, { + detail: { + ...detailData, + source: 'prebid-adapter', + libraryVersion: ADAPTER_VERSION, + }, + }) + ); +} + +/** + * Record and log a new error. + * + * @param {string} eventName - The name of the event to record + * @param {Error} err - Error object + * @param {any} data - Event data object + */ +function recordAndLogError(eventName, err, data) { + record(eventName, { ...data, error: err }); + logError(err.message); +} + +/** + * Validates whether a given account ID is valid. + * + * @param {string|number} accountID - The account ID to validate + * @returns {boolean} Returns true if the account ID is valid, false otherwise + */ +function isValidAccountID(accountID) { + // null/undefined are not acceptable + if (accountID == null) { + return false; + } + + // Numbers are valid (including 0) + if (isNumber(accountID)) { + return true; + } + + // Strings must have content after trimming + if (isStr(accountID)) { + return accountID.trim().length > 0; + } + + // Other types are invalid + return false; +} + +export const converter = ortbConverter({ + context: { + netRevenue: true, + }, + + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + // Remove precise geo locations for privacy. + if (request?.device?.geo) { + delete request.device.geo.lat; + delete request.device.geo.lon; + } + + if (request.user) { + // Remove sensitive user data. + delete request.user.gender; + delete request.user.yob; + // Remove both 'keywords' and alternate 'kwarry' if present. + delete request.user.keywords; + delete request.user.kwarry; + delete request.user.customdata; + delete request.user.geo; + delete request.user.data; + } + + request.ext = request.ext ?? {}; + request.ext.account = config.readConfig('aps.accountID'); + request.ext.sdk = { + version: ADAPTER_VERSION, + source: 'prebid', + }; + request.cur = request.cur ?? ['USD']; + + if (!request.imp || !Array.isArray(request.imp)) { + return request; + } + + request.imp.forEach((imp, index) => { + if (!imp) { + return; // continue to next iteration + } + + if (!imp.banner) { + return; // continue to next iteration + } + + const doesHWExist = imp.banner.w >= 0 && imp.banner.h >= 0; + const doesFormatExist = + Array.isArray(imp.banner.format) && imp.banner.format.length > 0; + + if (doesHWExist || !doesFormatExist) { + return; // continue to next iteration + } + + const { w, h } = imp.banner.format[0]; + + if (typeof w !== 'number' || typeof h !== 'number') { + return; // continue to next iteration + } + + imp.banner.w = w; + imp.banner.h = h; + }); + + return request; + }, + + bidResponse(buildBidResponse, bid, context) { + let vastUrl; + if (bid.mtype === 2) { + vastUrl = bid.adm; + // Making sure no adm value is passed down to prevent issues with some renderers + delete bid.adm; + } + + const bidResponse = buildBidResponse(bid, context); + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastUrl = vastUrl; + } + + return bidResponse; + }, +}); + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Validates the bid request. + * Always fires 100% of requests when account ID is valid. + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid: (bid) => { + record('isBidRequestValid'); + try { + const accountID = config.readConfig('aps.accountID'); + if (!isValidAccountID(accountID)) { + logWarn(`Invalid accountID: ${accountID}`); + return false; + } + return true; + } catch (err) { + err.message = `Error while validating bid request: ${err?.message}`; + recordAndLogError('isBidRequestValid/didError', err); + } + }, + + /** + * Constructs the server request for the bidder. + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: (bidRequests, bidderRequest) => { + record('buildRequests'); + try { + let endpoint = config.readConfig('aps.debugURL') ?? AAX_ENDPOINT; + // Append debug parameters to the URL if debug mode is enabled. + if (config.readConfig('aps.debug')) { + const debugQueryChar = endpoint.includes('?') ? '&' : '?'; + const renderMethod = config.readConfig('aps.renderMethod'); + if (renderMethod === 'fif') { + endpoint += debugQueryChar + 'amzn_debug_mode=fif&amzn_debug_mode=1'; + } else { + endpoint += debugQueryChar + 'amzn_debug_mode=1'; + } + } + return { + method: 'POST', + url: endpoint, + data: converter.toORTB({ bidRequests, bidderRequest }), + }; + } catch (err) { + err.message = `Error while building bid request: ${err?.message}`; + recordAndLogError('buildRequests/didError', err); + } + }, + + /** + * Interprets the response from the server. + * Constructs a creative script to render the ad using a prebid creative JS. + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[] | {bids: Bid[]}} + */ + interpretResponse: (response, request) => { + record('interpretResponse'); + try { + const interpretedResponse = converter.fromORTB({ + response: response.body, + request: request.data, + }); + const accountID = config.readConfig('aps.accountID'); + + const creativeUrl = + config.readConfig('aps.creativeURL') || DEFAULT_PREBID_CREATIVE_JS_URL; + + interpretedResponse.bids.forEach((bid) => { + if (bid.mediaType !== VIDEO) { + delete bid.ad; + bid.ad = ` +`.trim(); + } + }); + + return interpretedResponse.bids; + } catch (err) { + err.message = `Error while interpreting bid response: ${err?.message}`; + recordAndLogError('interpretResponse/didError', err); + } + }, + + /** + * Register user syncs to be processed during the shared user ID sync activity + * + * @param {Object} syncOptions - Options for user synchronization + * @param {Array} serverResponses - Array of bid responses + * @param {Object} gdprConsent - GDPR consent information + * @param {Object} uspConsent - USP consent information + * @returns {Array} Array of user sync objects + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + record('getUserSyncs'); + try { + if (hasPurpose1Consent(gdprConsent)) { + return serverResponses + .flatMap((res) => res?.body?.ext?.userSyncs ?? []) + .filter( + (s) => + (s.type === 'iframe' && syncOptions.iframeEnabled) || + (s.type === 'image' && syncOptions.pixelEnabled) + ); + } + } catch (err) { + err.message = `Error while getting user syncs: ${err?.message}`; + recordAndLogError('getUserSyncs/didError', err); + } + }, + + onTimeout: (timeoutData) => { + record('onTimeout', { error: timeoutData }); + }, + + onSetTargeting: (bid) => { + record('onSetTargeting'); + }, + + onAdRenderSucceeded: (bid) => { + record('onAdRenderSucceeded'); + }, + + onBidderError: (error) => { + record('onBidderError', { error }); + }, + + onBidWon: (bid) => { + record('onBidWon'); + }, + + onBidAttribute: (bid) => { + record('onBidAttribute'); + }, + + onBidBillable: (bid) => { + record('onBidBillable'); + }, +}; + +registerBidder(spec); diff --git a/modules/apsBidAdapter.md b/modules/apsBidAdapter.md new file mode 100644 index 00000000000..1b772210af7 --- /dev/null +++ b/modules/apsBidAdapter.md @@ -0,0 +1,84 @@ +# Overview + +``` +Module Name: APS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: aps-prebid@amazon.com +``` + +# Description + +Connects to Amazon Publisher Services (APS) for bids. + +## Test Bids + +Please contact your APS Account Manager to learn more about our testing policies. + +# Usage + +## Prerequisites + +Add the account ID provided by APS to your configuration. + +``` +pbjs.setBidderConfig( + { + bidders: ['aps'], + config: { + aps: { + accountID: YOUR_APS_ACCOUNT_ID, + } + }, + }, + true // mergeConfig toggle +); +``` + +## Ad Units + +## Banner + +``` +const adUnits = [ + { + code: 'banner_div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [{ bidder: 'aps' }], + }, +]; +``` + +## Video + +Please select your preferred video renderer. The following example uses in-renderer-js: + +``` +const adUnits = [ + { + code: 'video_div', + mediaTypes: { + video: { + playerSize: [400, 225], + context: 'outstream', + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + minduration: 5, + maxduration: 30, + placement: 3, + }, + }, + bids: [{ bidder: 'aps' }], + renderer: { + url: 'https://cdn.jsdelivr.net/npm/in-renderer-js@1/dist/in-renderer.umd.min.js', + render(bid) { + new window.InRenderer().render('video_div', bid); + }, + }, + }, +]; + +``` diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 838487925c8..f432c85388f 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -1,5 +1,6 @@ import {getDNT} from '../libraries/dnt/index.js'; import { generateUUID, deepAccess, createTrackPixelHtml } from '../src/utils.js'; +import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -335,7 +336,7 @@ function injectPixels(ad, pixels, scripts) { } function getScreenParams() { - return `${window.screen.width}x${window.screen.height}@${window.devicePixelRatio}`; + return `${window.screen.width}x${window.screen.height}@${getDevicePixelRatio(window)}`; } function getBids(bids) { diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 17b7d9bd8df..c0b3f334c40 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {ajax} from '../src/ajax.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; const BIDDER_CODE = 'axonix'; const BIDDER_VERSION = '1.0.2'; @@ -81,19 +82,9 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { // device.connectiontype - const connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection) - let connectionType = 'unknown'; - let effectiveType = ''; - - if (connection) { - if (connection.type) { - connectionType = connection.type; - } - - if (connection.effectiveType) { - effectiveType = connection.effectiveType; - } - } + const connection = getConnectionInfo(); + const connectionType = connection?.type ?? 'unknown'; + const effectiveType = connection?.effectiveType ?? ''; const requests = validBidRequests.map(validBidRequest => { // app/site diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 6cb9b6dfcc8..3f627fe39e0 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -11,6 +11,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import { getFirstSize, getOsVersion, getVideoSizes, getBannerSizes, isConnectedTV, getDoNotTrack, isMobile, isBannerBid, isVideoBid, getBannerBidFloor, getVideoBidFloor, getVideoTargetingParams, getTopWindowLocation } from '../libraries/advangUtils/index.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; const ADAPTER_VERSION = '1.21'; const GVLID = 157; @@ -338,8 +339,8 @@ function createVideoRequestData(bid, bidderRequest) { deepSetValue(payload, 'user.ext.eids', eids); } - const connection = navigator.connection || navigator.webkitConnection; - if (connection && connection.effectiveType) { + const connection = getConnectionInfo(); + if (connection?.effectiveType) { deepSetValue(payload, 'device.connectiontype', connection.effectiveType); } diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 16a67a42432..13089b5be72 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -192,6 +192,40 @@ function buildTrackingParams(data, info, value) { }; } +function normalizeAdUnitCode(adUnitCode) { + if (!adUnitCode || typeof adUnitCode !== 'string') return undefined; + + // Only normalize GPT auto-generated adUnitCodes (div-gpt-ad-*) + // For non-GPT codes, return original string unchanged to preserve case + if (!/^div-gpt-ad[-_]/i.test(adUnitCode)) { + return adUnitCode; + } + + // GPT handling: strip prefix and random suffix + let slot = adUnitCode; + slot = slot.replace(/^div-gpt-ad[-_]?/i, ''); + + /** + * Remove only long numeric suffixes (likely auto-generated IDs). + * Preserve short numeric suffixes as they may be meaningful slot indices. + * + * Examples removed: + * div-gpt-ad-article_top_123456 → article_top + * div-gpt-ad-sidebar-1678459238475 → sidebar + * + * Examples preserved: + * div-gpt-ad-topbanner-1 → topbanner-1 + * div-gpt-ad-topbanner-2 → topbanner-2 + */ + slot = slot.replace(/([_-])\d{6,}$/, ''); + + slot = slot.toLowerCase().trim(); + + if (slot.length < 3) return undefined; + + return slot; +} + function beOpRequestSlotsMaker(bid, bidderRequest) { const bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); const publisherCurrency = getCurrencyFromBidderRequest(bidderRequest) || getValue(bid.params, 'currency') || 'EUR'; @@ -211,7 +245,11 @@ function beOpRequestSlotsMaker(bid, bidderRequest) { nptnid: getValue(bid.params, 'networkPartnerId'), bid: getBidIdParameter('bidId', bid), brid: getBidIdParameter('bidderRequestId', bid), - name: getBidIdParameter('adUnitCode', bid), + name: deepAccess(bid, 'ortb2Imp.ext.gpid') || + deepAccess(bid, 'ortb2Imp.ext.data.adslot') || + deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot') || + bid.ortb2Imp?.tagid || + normalizeAdUnitCode(bid.adUnitCode), tid: bid.ortb2Imp?.ext?.tid || '', brc: getBidIdParameter('bidRequestsCount', bid), bdrc: getBidIdParameter('bidderRequestCount', bid), diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index 64026958bc6..dc131e0bca1 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -29,7 +29,7 @@ export function reset() { } export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { - const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; + const {bcat = [], badv = [], cattax = 1} = index.getOrtb2(bid) || {}; const bidRequest = index.getBidRequest(bid); const battr = bidRequest?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; @@ -43,11 +43,15 @@ export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctio advertiserDomains = [], attr: metaAttr, mediaType: metaMediaType, + cattax: metaCattax = 1, } = bid.meta || {}; // checking if bid fulfills ortb2 fields rules - if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || - (catConfig.blockUnknown && !primaryCatId)) { + const normalizedMetaCattax = Number(metaCattax); + const normalizedRequestCattax = Number(cattax); + const isCattaxMatch = normalizedMetaCattax === normalizedRequestCattax; + if ((catConfig.enforce && isCattaxMatch && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.blockUnknown && (!isCattaxMatch || !primaryCatId))) { reject(BID_CATEGORY_REJECTION_REASON); } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || (advConfig.blockUnknown && !advertiserDomains.length)) { diff --git a/modules/bidmaticBidAdapter.js b/modules/bidmaticBidAdapter.js index 4265b48428f..daa379421b6 100644 --- a/modules/bidmaticBidAdapter.js +++ b/modules/bidmaticBidAdapter.js @@ -4,7 +4,6 @@ import { cleanObj, deepAccess, flatten, - getWinDimensions, isArray, isNumber, logWarn, @@ -13,7 +12,7 @@ import { import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { chunk } from '../libraries/chunk/chunk.js'; -import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import {getPlacementPositionUtils} from "../libraries/placementPositionInfo/placementPositionInfo.js"; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -21,10 +20,13 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec */ +const ADAPTER_VERSION = 'v1.0.0'; const URL = 'https://adapter.bidmatic.io/bdm/auction'; const BIDDER_CODE = 'bidmatic'; const SYNCS_DONE = new Set(); +const { getPlacementEnv, getPlacementInfo } = getPlacementPositionUtils() + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, @@ -144,6 +146,7 @@ export function parseResponseBody(serverResponse, adapterRequest) { export function remapBidRequest(bidRequests, adapterRequest) { const bidRequestBody = { + AdapterVersion: ADAPTER_VERSION, Domain: deepAccess(adapterRequest, 'refererInfo.page'), ...getPlacementEnv() }; @@ -237,50 +240,4 @@ export function createBid(bidResponse) { }; } -function getPlacementInfo(bidReq) { - const placementElementNode = document.getElementById(bidReq.adUnitCode); - try { - return cleanObj({ - AuctionsCount: bidReq.auctionsCount, - DistanceToView: getViewableDistance(placementElementNode) - }); - } catch (e) { - logWarn('Error while getting placement info', e); - return {}; - } -} - -/** - * @param element - */ -function getViewableDistance(element) { - if (!element) return 0; - const elementRect = getBoundingClientRect(element); - - if (!elementRect) { - return 0; - } - - const elementMiddle = elementRect.top + (elementRect.height / 2); - const viewportHeight = getWinDimensions().innerHeight - if (elementMiddle > window.scrollY + viewportHeight) { - // element is below the viewport - return Math.round(elementMiddle - (window.scrollY + viewportHeight)); - } - // element is above the viewport -> negative value - return Math.round(elementMiddle); -} - -function getPageHeight() { - return document.documentElement.scrollHeight || document.body.scrollHeight; -} - -function getPlacementEnv() { - return cleanObj({ - TimeFromNavigation: Math.floor(performance.now()), - TabActive: document.visibilityState === 'visible', - PageHeight: getPageHeight() - }) -} - registerBidder(spec); diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index 0cddb617799..80e98dbd37e 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -45,33 +45,82 @@ export const spec = { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const adUnits = []; - var bidderUrl = REQUEST_ENDPOINT + Math.random(); + const bidderUrl = REQUEST_ENDPOINT + Math.random(); _each(validBidRequests, function (bid) { + const passthrough = bid.ortb2Imp?.ext?.prebid?.passthrough; + const filteredPassthrough = passthrough ? Object.fromEntries( + Object.entries({ + bucket: passthrough.bucket, + client: passthrough.client, + gamAdCode: passthrough.gamAdCode, + gamLoc: passthrough.gamLoc, + colo: passthrough.colo, + device: passthrough.device, + lang: passthrough.lang, + pt: passthrough.pt, + region: passthrough.region, + site: passthrough.site, + ver: passthrough.ver + }).filter(([_, value]) => value !== undefined) + ) : undefined; + const adUnit = { adUnitCode: bid.adUnitCode, requestId: bid.bidId, + transactionId: bid.transactionId, + adUnitId: bid.adUnitId, + sizes: bid.sizes, mediaTypes: bid.mediaTypes || { banner: { sizes: bid.sizes } }, - userIds: bid.userId || {}, - userIdAsEids: bid.userIdAsEids || {} + ortb2Imp: { + ext: { + prebid: { + passthrough: filteredPassthrough + }, + data: { + adserver: { + name: bid.ortb2Imp?.ext?.data?.adserver?.name, + adslot: bid.ortb2Imp?.ext?.data?.adserver?.adslot + }, + pbadslot: bid.ortb2Imp?.ext?.data?.pbadslot + }, + gpid: bid.ortb2Imp?.ext?.gpid + }, + banner: { + pos: bid.ortb2Imp?.banner?.pos + } + } }; - if (bid.params.cid) { + + if (bid.params?.cid) { adUnit.cid = bid.params.cid; - } else { + } else if (bid.params?.ChannelID) { adUnit.ChannelID = bid.params.ChannelID; } + + let floorInfo = {}; + if (typeof bid.getFloor === 'function') { + const mediaType = bid.mediaTypes?.banner ? BANNER : (bid.mediaTypes?.native ? NATIVE : '*'); + const sizes = bid.mediaTypes?.banner?.sizes || bid.sizes || []; + const size = sizes.length === 1 ? sizes[0] : '*'; + floorInfo = bid.getFloor({currency: 'USD', mediaType: mediaType, size: size}) || {}; + } + adUnit.floor = floorInfo.floor; + adUnit.currency = floorInfo.currency; adUnits.push(adUnit); }); let topUrl = ''; - if (bidderRequest && bidderRequest.refererInfo) { + if (bidderRequest?.refererInfo?.page) { topUrl = bidderRequest.refererInfo.page; } + const firstBid = validBidRequests[0] || {}; + return { method: 'POST', url: bidderUrl, @@ -82,10 +131,23 @@ export const spec = { }, inIframe: inIframe(), url: topUrl, - referrer: bidderRequest.refererInfo.ref, + referrer: bidderRequest?.refererInfo?.ref, + auctionId: firstBid?.auctionId, + bidderRequestId: firstBid?.bidderRequestId, + src: firstBid?.src, + userIds: firstBid?.userId || {}, + userIdAsEids: firstBid?.userIdAsEids || [], + auctionsCount: firstBid?.auctionsCount, + bidRequestsCount: firstBid?.bidRequestsCount, + bidderRequestsCount: firstBid?.bidderRequestsCount, + bidderWinsCount: firstBid?.bidderWinsCount, + deferBilling: firstBid?.deferBilling, + metrics: firstBid?.metrics || {}, adUnits: adUnits, // TODO: please do not send internal data structures over the network - refererInfo: bidderRequest.refererInfo.legacy}, + refererInfo: bidderRequest?.refererInfo?.legacy, + ortb2: bidderRequest?.ortb2 + }, validBidRequests: validBidRequests }; }, diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index ddc1112796d..265dcf2115b 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -11,6 +11,7 @@ import { ajax } from '../src/ajax.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; import { getViewportSize } from '../libraries/viewport/viewport.js'; import { getOsBrowserInfo } from '../libraries/userAgentUtils/detailed.js'; +import { getTimeZone } from '../libraries/timezone/timezone.js'; const versionCode = '4.4.1' const secretKey = 'bydata@123456' @@ -234,7 +235,7 @@ ascAdapter.getVisitorData = function (data = {}) { ua["brv"] = info.browser.version; ua['ss'] = screenSize; ua['de'] = deviceType; - ua['tz'] = window.Intl.DateTimeFormat().resolvedOptions().timeZone; + ua['tz'] = getTimeZone(); } var signedToken = getJWToken(ua); payload['visitor_data'] = signedToken; diff --git a/modules/chromeAiRtdProvider.js b/modules/chromeAiRtdProvider.js index 98d429af936..75f17b6312a 100644 --- a/modules/chromeAiRtdProvider.js +++ b/modules/chromeAiRtdProvider.js @@ -1,7 +1,7 @@ import { submodule } from '../src/hook.js'; import { logError, mergeDeep, logMessage, deepSetValue, deepAccess } from '../src/utils.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /* global LanguageDetector, Summarizer */ /** @@ -14,6 +14,8 @@ export const CONSTANTS = Object.freeze({ LOG_PRE_FIX: 'ChromeAI-Rtd-Provider:', STORAGE_KEY: 'chromeAi_detected_data', // Single key for both language and keywords MIN_TEXT_LENGTH: 20, + ACTIVATION_EVENTS: ['click', 'keydown', 'mousedown', 'touchend', 'pointerdown', 'pointerup'], + MAX_TEXT_LENGTH: 1000, // Limit to prevent QuotaExceededError with Chrome AI APIs DEFAULT_CONFIG: { languageDetector: { enabled: true, @@ -31,7 +33,7 @@ export const CONSTANTS = Object.freeze({ } }); -export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: CONSTANTS.SUBMODULE_NAME}); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: CONSTANTS.SUBMODULE_NAME }); let moduleConfig = JSON.parse(JSON.stringify(CONSTANTS.DEFAULT_CONFIG)); let detectedKeywords = null; // To store generated summary/keywords @@ -93,6 +95,11 @@ export const getPageText = () => { logMessage(`${CONSTANTS.LOG_PRE_FIX} Not enough text content (length: ${text?.length || 0}) for processing.`); return null; } + // Limit text length to prevent QuotaExceededError with Chrome AI APIs + if (text.length > CONSTANTS.MAX_TEXT_LENGTH) { + logMessage(`${CONSTANTS.LOG_PRE_FIX} Truncating text from ${text.length} to ${CONSTANTS.MAX_TEXT_LENGTH} chars.`); + return text.substring(0, CONSTANTS.MAX_TEXT_LENGTH); + } return text; }; @@ -299,6 +306,30 @@ const initSummarizer = async () => { return false; } + // If the model is not 'available' (needs download), it typically requires a user gesture. + // We check availability and defer if needed. + try { + const availability = await Summarizer.availability(); + const needsDownload = availability !== 'available' && availability !== 'unavailable'; // 'after-download', 'downloading', etc. + + if (needsDownload && !navigator.userActivation?.isActive) { + logMessage(`${CONSTANTS.LOG_PRE_FIX} Summarizer needs download (${availability}) but user inactive. Deferring init...`); + + const onUserActivation = () => { + CONSTANTS.ACTIVATION_EVENTS.forEach(evt => window.removeEventListener(evt, onUserActivation)); + logMessage(`${CONSTANTS.LOG_PRE_FIX} User activation detected. Retrying initSummarizer...`); + // Retry initialization with fresh gesture + initSummarizer(); + }; + + CONSTANTS.ACTIVATION_EVENTS.forEach(evt => window.addEventListener(evt, onUserActivation, { once: true })); + + return false; // Return false to not block main init, will retry later + } + } catch (e) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error checking Summarizer availability:`, e); + } + const summaryText = await detectSummary(pageText, moduleConfig.summarizer); if (summaryText) { // The API returns a single summary string. We treat this string as a single keyword. diff --git a/modules/clickioBidAdapter.js b/modules/clickioBidAdapter.js index 954888291c3..3ba5094ffe5 100644 --- a/modules/clickioBidAdapter.js +++ b/modules/clickioBidAdapter.js @@ -4,6 +4,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {BANNER} from '../src/mediaTypes.js'; const BIDDER_CODE = 'clickio'; +const IAB_GVL_ID = 1500; export const converter = ortbConverter({ context: { @@ -19,6 +20,7 @@ export const converter = ortbConverter({ export const spec = { code: BIDDER_CODE, + gvlid: IAB_GVL_ID, supportedMediaTypes: [BANNER], buildRequests(bidRequests, bidderRequest) { const data = converter.toORTB({bidRequests, bidderRequest}) diff --git a/modules/clickioBidAdapter.md b/modules/clickioBidAdapter.md index 5d3d41488ff..7667ebe0ffd 100644 --- a/modules/clickioBidAdapter.md +++ b/modules/clickioBidAdapter.md @@ -5,6 +5,8 @@ description: Clickio Bidder Adapter biddercode: clickio media_types: banner gdpr_supported: true +tcfeu_supported: true +gvl_id: 1500 usp_supported: true gpp_supported: true schain_supported: true diff --git a/modules/clydoBidAdapter.js b/modules/clydoBidAdapter.js new file mode 100644 index 00000000000..1ffdd3df474 --- /dev/null +++ b/modules/clydoBidAdapter.js @@ -0,0 +1,101 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepSetValue, deepAccess, isFn } from '../src/utils.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'clydo'; +const METHOD = 'POST'; +const DEFAULT_CURRENCY = 'USD'; +const params = { + region: "{{region}}", + partnerId: "{{partnerId}}" +} +const BASE_ENDPOINT_URL = `https://${params.region}.clydo.io/${params.partnerId}` + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.mediaType'); + return buildBidResponse(bid, context) + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid: function(bid) { + if (!bid || !bid.params) return false; + const { partnerId, region } = bid.params; + if (typeof partnerId !== 'string' || partnerId.length === 0) return false; + if (typeof region !== 'string') return false; + const allowedRegions = ['us', 'usw', 'eu', 'apac']; + return allowedRegions.includes(region); + }, + buildRequests: function(validBidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests: validBidRequests, bidderRequest}); + const { partnerId, region } = validBidRequests[0].params; + + if (Array.isArray(data.imp)) { + data.imp.forEach((imp, index) => { + const srcBid = validBidRequests[index] || validBidRequests[0]; + const bidderParams = deepAccess(srcBid, 'params') || {}; + deepSetValue(data, `imp.${index}.ext.clydo`, bidderParams); + + const mediaType = imp.banner ? 'banner' : (imp.video ? 'video' : (imp.native ? 'native' : '*')); + let floor = deepAccess(srcBid, 'floor'); + if (!floor && isFn(srcBid.getFloor)) { + const floorInfo = srcBid.getFloor({currency: DEFAULT_CURRENCY, mediaType, size: '*'}); + if (floorInfo && typeof floorInfo.floor === 'number') { + floor = floorInfo.floor; + } + } + + if (typeof floor === 'number') { + deepSetValue(data, `imp.${index}.bidfloor`, floor); + deepSetValue(data, `imp.${index}.bidfloorcur`, DEFAULT_CURRENCY); + } + }); + } + + const ENDPOINT_URL = BASE_ENDPOINT_URL + .replace(params.partnerId, partnerId) + .replace(params.region, region); + + return [{ + method: METHOD, + url: ENDPOINT_URL, + data + }] + }, + interpretResponse: function(serverResponse, request) { + let bids = []; + let body = serverResponse.body || {}; + if (body) { + const normalized = Array.isArray(body.seatbid) + ? { + ...body, + seatbid: body.seatbid.map(seat => ({ + ...seat, + bid: (seat.bid || []).map(b => { + if (typeof b?.adm === 'string') { + try { + const parsed = JSON.parse(b.adm); + if (parsed && parsed.native && Array.isArray(parsed.native.assets)) { + return {...b, adm: JSON.stringify(parsed.native)}; + } + } catch (e) {} + } + return b; + }) + })) + } + : body; + bids = converter.fromORTB({response: normalized, request: request.data}).bids; + } + return bids; + }, +} +registerBidder(spec); diff --git a/modules/clydoBidAdapter.md b/modules/clydoBidAdapter.md new file mode 100644 index 00000000000..a7ec0b57800 --- /dev/null +++ b/modules/clydoBidAdapter.md @@ -0,0 +1,93 @@ +# Overview + +``` +Module Name: Clydo Bid Adapter +Module Type: Bidder Adapter +Maintainer: cto@clydo.io +``` + +# Description + +The Clydo adapter connects to the Clydo bidding endpoint to request bids using OpenRTB. + +- Supported media types: banner, video, native +- Endpoint is derived from parameters: `https://{region}.clydo.io/{partnerId}` +- Passes GDPR, USP/CCPA, and GPP consent when available +- Propagates `schain` and `userIdAsEids` + +# Bid Params + +- `partnerId` (string, required): Partner identifier provided by Clydo +- `region` (string, required): One of `us`, `usw`, `eu`, `apac` + +# Test Parameters (Banner) +```javascript +var adUnits = [{ + code: '/15185185/prebid_banner_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'clydo', + params: { + partnerId: 'abcdefghij', + region: 'us' + } + }] +}]; +``` + +# Test Parameters (Video) +```javascript +var adUnits = [{ + code: '/15185185/prebid_video_example_1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + mimes: ['video/mp4'] + } + }, + bids: [{ + bidder: 'clydo', + params: { + partnerId: 'abcdefghij', + region: 'us' + } + }] +}]; +``` + +# Test Parameters (Native) +```javascript +var adUnits = [{ + code: '/15185185/prebid_native_example_1', + mediaTypes: { + native: { + title: { required: true }, + image: { required: true, sizes: [120, 120] }, + icon: { required: false, sizes: [50, 50] }, + body: { required: false }, + sponsoredBy: { required: false }, + clickUrl: { required: false }, + cta: { required: false } + } + }, + bids: [{ + bidder: 'clydo', + params: { + partnerId: 'abcdefghij', + region: 'us' + } + }] +}]; +``` + +# Notes + +- Floors: If the ad unit implements `getFloor`, the adapter forwards the value as `imp.bidfloor` (USD). +- Consent: When present, the adapter forwards `gdprApplies`/`consentString`, `uspConsent`, and `gpp`/`gpp_sid`. +- Supply Chain and IDs: `schain` is set under `source.ext.schain`; user IDs are forwarded under `user.ext.eids`. + diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 67ebd88e4e4..eef57e0aaa8 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -1,81 +1,258 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -// import { logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; const BIDDER_CODE = 'conceptx'; -const ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; -// const LOG_PREFIX = 'ConceptX: '; +const ENDPOINT_URL = 'https://cxba-s2s.cncpt.dk/openrtb2/auction'; const GVLID = 1340; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], gvlid: GVLID, + isBidRequestValid: function (bid) { - return !!(bid.bidId && bid.params.site && bid.params.adunit); + return !!(bid.bidId && bid.params && bid.params.adunit); }, buildRequests: function (validBidRequests, bidderRequest) { - // logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); const requests = []; - let requestUrl = `${ENDPOINT_URL}` - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - requestUrl += '?gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; - requestUrl += '&consentString=' + bidderRequest.gdprConsent.consentString; - } - for (var i = 0; i < validBidRequests.length; i++) { - const requestParent = { adUnits: [], meta: {} }; - const bid = validBidRequests[i] - const { adUnitCode, auctionId, bidId, bidder, bidderRequestId, ortb2 } = bid - requestParent.meta = { adUnitCode, auctionId, bidId, bidder, bidderRequestId, ortb2 } - - const { site, adunit } = bid.params - const adUnit = { site, adunit, targetId: bid.bidId } - if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes - requestParent.adUnits.push(adUnit); + + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + const { + adUnitCode, + auctionId, + bidId, + bidder, + bidderRequestId, + ortb2 = {}, + } = bid; + const params = bid.params || {}; + + // PBS URL + GDPR query params + let url = ENDPOINT_URL; + const query = []; + + // Only add GDPR params when gdprApplies is explicitly 0 or 1 + if (bidderRequest && bidderRequest.gdprConsent) { + let gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (typeof gdprApplies === 'boolean') { + gdprApplies = gdprApplies ? 1 : 0; + } + if (gdprApplies === 0 || gdprApplies === 1) { + query.push('gdpr_applies=' + gdprApplies); + if (bidderRequest.gdprConsent.consentString) { + query.push( + 'gdpr_consent=' + + encodeURIComponent(bidderRequest.gdprConsent.consentString) + ); + } + } + } + + if (query.length) { + url += '?' + query.join('&'); + } + + // site + const page = + params.site || (ortb2.site && ortb2.site.page) || ''; + const domain = + params.domain || (ortb2.site && ortb2.site.domain) || page; + + const site = { + id: domain || page || adUnitCode, + domain: domain || '', + page: page || '', + }; + + // banner sizes from mediaTypes.banner.sizes + const formats = []; + if ( + bid.mediaTypes && + bid.mediaTypes.banner && + bid.mediaTypes.banner.sizes + ) { + let sizes = bid.mediaTypes.banner.sizes; + if (sizes.length && typeof sizes[0] === 'number') { + sizes = [sizes]; + } + for (let j = 0; j < sizes.length; j++) { + const size = sizes[j]; + if (size && size.length === 2) { + formats.push({ w: size[0], h: size[1] }); + } + } + } + + const banner = formats.length ? { format: formats } : {}; + + // currency & timeout + let currency = 'DKK'; + if ( + bidderRequest && + bidderRequest.currency && + bidderRequest.currency.adServerCurrency + ) { + currency = bidderRequest.currency.adServerCurrency; + } + + const tmax = (bidderRequest && bidderRequest.timeout) || 500; + + // device + const ua = + typeof navigator !== 'undefined' && navigator.userAgent + ? navigator.userAgent + : 'Mozilla/5.0'; + const device = { ua }; + + // build OpenRTB request for PBS with stored requests + const ortbRequest = { + id: auctionId || bidId, + site, + device, + cur: [currency], + tmax, + imp: [ + { + id: bidId, + banner, + ext: { + prebid: { + storedrequest: { + id: params.adunit, + }, + }, + }, + }, + ], + ext: { + prebid: { + storedrequest: { + id: 'cx_global', + }, + custommeta: { + adUnitCode, + auctionId, + bidId, + bidder, + bidderRequestId, + }, + }, + }, + }; + + // GDPR in body + if (bidderRequest && bidderRequest.gdprConsent) { + let gdprAppliesBody = bidderRequest.gdprConsent.gdprApplies; + if (typeof gdprAppliesBody === 'boolean') { + gdprAppliesBody = gdprAppliesBody ? 1 : 0; + } + + if (!ortbRequest.user) ortbRequest.user = {}; + if (!ortbRequest.user.ext) ortbRequest.user.ext = {}; + + if (bidderRequest.gdprConsent.consentString) { + ortbRequest.user.ext.consent = + bidderRequest.gdprConsent.consentString; + } + + if (!ortbRequest.regs) ortbRequest.regs = {}; + if (!ortbRequest.regs.ext) ortbRequest.regs.ext = {}; + + if (gdprAppliesBody === 0 || gdprAppliesBody === 1) { + ortbRequest.regs.ext.gdpr = gdprAppliesBody; + } + } + + // user IDs -> user.ext.eids + if (bid.userIdAsEids && bid.userIdAsEids.length) { + if (!ortbRequest.user) ortbRequest.user = {}; + if (!ortbRequest.user.ext) ortbRequest.user.ext = {}; + ortbRequest.user.ext.eids = bid.userIdAsEids; + } + requests.push({ method: 'POST', - url: requestUrl, + url, options: { - withCredentials: false, + withCredentials: true, }, - data: JSON.stringify(requestParent), + data: JSON.stringify(ortbRequest), }); } return requests; }, - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - const bidResponsesFromServer = serverResponse.body.bidResponses; - if (Array.isArray(bidResponsesFromServer) && bidResponsesFromServer.length === 0) { - return bidResponses - } - const firstBid = bidResponsesFromServer[0] - if (!firstBid) { - return bidResponses + interpretResponse: function (serverResponse, request) { + const body = + serverResponse && serverResponse.body ? serverResponse.body : {}; + + // PBS OpenRTB: seatbid[].bid[] + if ( + !body.seatbid || + !Array.isArray(body.seatbid) || + body.seatbid.length === 0 + ) { + return []; } - const firstSeat = firstBid.ads[0] - if (!firstSeat) { - return bidResponses + + const currency = body.cur || 'DKK'; + const bids = []; + + // recover referrer (site.page) from original request + let referrer = ''; + try { + if (request && request.data) { + const originalReq = + typeof request.data === 'string' + ? JSON.parse(request.data) + : request.data; + if (originalReq && originalReq.site && originalReq.site.page) { + referrer = originalReq.site.page; + } + } + } catch (_) {} + + for (let i = 0; i < body.seatbid.length; i++) { + const seatbid = body.seatbid[i]; + if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue; + + for (let j = 0; j < seatbid.bid.length; j++) { + const b = seatbid.bid[j]; + + if (!b || typeof b.price !== 'number' || !b.adm) continue; + + bids.push({ + requestId: b.impid || b.id, + cpm: b.price, + width: b.w, + height: b.h, + creativeId: b.crid || b.id || '', + dealId: b.dealid || b.dealId || undefined, + currency, + netRevenue: true, + ttl: 300, + referrer, + ad: b.adm, + }); + } } - const bidResponse = { - requestId: firstSeat.requestId, - cpm: firstSeat.cpm, - width: firstSeat.width, - height: firstSeat.height, - creativeId: firstSeat.creativeId, - dealId: firstSeat.dealId, - currency: firstSeat.currency, - netRevenue: true, - ttl: firstSeat.ttl, - referrer: firstSeat.referrer, - ad: firstSeat.html - }; - bidResponses.push(bidResponse); - return bidResponses; + + return bids; + }, + + /** + * Cookie sync for conceptx is handled by the enrichment script's runPbsCookieSync, + * which calls https://cxba-s2s.cncpt.dk/cookie_sync with bidders. The PBS returns + * bidder_status with usersync URLs, and the script runs iframe/image syncs. + * The adapter does not return sync URLs here since those come from the cookie_sync + * endpoint, not the auction response. + */ + getUserSyncs: function () { + return []; }, +}; -} registerBidder(spec); diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 5ea7d88fa80..7b22de3aa1d 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -31,8 +31,7 @@ const BIDDER_CODE = 'connatix'; const AD_URL = 'https://capi.connatix.com/rtb/hba'; const DEFAULT_MAX_TTL = '3600'; const DEFAULT_CURRENCY = 'USD'; -const CNX_IDS_LOCAL_STORAGE_COOKIES_KEY = 'cnx_user_ids'; -const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000; // 30 days +const CNX_IDS_LOCAL_STORAGE_KEY = 'cnx_user_ids'; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved'; const IDENTITY_PROVIDER_COLLECTION_UPDATED_EVENT = 'cnx_identity_provider_collection_updated'; @@ -197,23 +196,16 @@ export function hasQueryParams(url) { } } -export function saveOnAllStorages(name, value, expirationTimeMs) { - const date = new Date(); - date.setTime(date.getTime() + expirationTimeMs); - const expires = `expires=${date.toUTCString()}`; - storage.setCookie(name, JSON.stringify(value), expires); +export function saveInLocalStorage(name, value) { storage.setDataInLocalStorage(name, JSON.stringify(value)); cnxIdsValues = value; } -export function readFromAllStorages(name) { - const fromCookie = storage.getCookie(name); +export function readFromLocalStorage(name) { const fromLocalStorage = storage.getDataFromLocalStorage(name); - - const parsedCookie = fromCookie ? JSON.parse(fromCookie) : undefined; const parsedLocalStorage = fromLocalStorage ? JSON.parse(fromLocalStorage) : undefined; - return parsedCookie || parsedLocalStorage || undefined; + return parsedLocalStorage || undefined; } export const spec = { @@ -261,7 +253,7 @@ export const spec = { const bidRequests = _getBidRequests(validBidRequests); let userIds; try { - userIds = readFromAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY) || cnxIdsValues; + userIds = readFromLocalStorage(CNX_IDS_LOCAL_STORAGE_KEY) || cnxIdsValues; } catch (error) { userIds = cnxIdsValues; } @@ -350,6 +342,15 @@ export const spec = { params['us_privacy'] = encodeURIComponent(uspConsent); } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + params['gpp'] = encodeURIComponent(gppConsent.gppString); + params['gpp_sid'] = gppConsent.applicableSections.join(','); + } + + if (config.getConfig('coppa') === true) { + params['coppa'] = 1; + } + window.addEventListener('message', function handler(event) { if (!event.data || event.origin !== 'https://cds.connatix.com' || !event.data.cnx) { return; @@ -364,7 +365,7 @@ export const spec = { if (message === ALL_PROVIDERS_RESOLVED_EVENT || message === IDENTITY_PROVIDER_COLLECTION_UPDATED_EVENT) { if (data) { - saveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, data, CNX_IDS_EXPIRY); + saveInLocalStorage(CNX_IDS_LOCAL_STORAGE_KEY, data); } } }, true) diff --git a/modules/dasBidAdapter.js b/modules/dasBidAdapter.js new file mode 100644 index 00000000000..5d85644531f --- /dev/null +++ b/modules/dasBidAdapter.js @@ -0,0 +1,373 @@ +import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; +import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { deepAccess, safeJSONParse } from '../src/utils.js'; + +const BIDDER_CODE = 'das'; +const GDE_SCRIPT_URL = 'https://ocdn.eu/adp/static/embedgde/latest/bundle.min.js'; +const GDE_PARAM_PREFIX = 'gde_'; +const REQUIRED_GDE_PARAMS = [ + 'gde_subdomena', + 'gde_id', + 'gde_stparam', + 'gde_fastid', + 'gde_inscreen' +]; + +function parseNativeResponse(ad) { + if (!(ad.data?.fields && ad.data?.meta)) { + return false; + } + + const { click, Thirdpartyimpressiontracker, Thirdpartyimpressiontracker2, thirdPartyClickTracker2, imp, impression, impression1, impressionJs1, 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 emsLink = ad.ems_link; + const link = adclick + (url || click); + const nativeResponse = { + sendTargetingKeys: false, + title: title || Headline || '', + image: { + url: image || Image || '', + width, + height + }, + icon: { + url: partnerLogo || '', + width, + height + }, + clickUrl: link, + cta: Calltoaction || '', + body: leadtext || Body || '', + body2: adInfo || '', + sponsoredBy: deepAccess(ad, 'data.meta.advertiser_name', null) || '', + }; + + nativeResponse.impressionTrackers = [emsLink, imp, impression, impression1, Thirdpartyimpressiontracker, Thirdpartyimpressiontracker2].filter(Boolean); + nativeResponse.javascriptTrackers = [impressionJs1, getGdeScriptUrl(ad.data.fields)].map(url => url ? `` : null).filter(Boolean); + nativeResponse.clickTrackers = [Thirdpartyclicktracker, thirdPartyClickTracker2].filter(Boolean); + + if (dsaurl) { + nativeResponse.privacyLink = dsaurl; + } + + return nativeResponse +} + +function getGdeScriptUrl(adDataFields) { + if (REQUIRED_GDE_PARAMS.every(param => adDataFields[param])) { + const params = new URLSearchParams(); + Object.entries(adDataFields) + .filter(([key]) => key.startsWith(GDE_PARAM_PREFIX)) + .forEach(([key, value]) => params.append(key, value)); + + return `${GDE_SCRIPT_URL}?${params.toString()}`; + } + return null; +} + +function getEndpoint(network) { + return `https://csr.onet.pl/${encodeURIComponent(network)}/bid`; +} + +function parseParams(params, bidderRequest) { + const customParams = {}; + const keyValues = {}; + + if (params.adbeta) { + customParams.adbeta = params.adbeta; + } + + if (params.site) { + customParams.site = params.site; + } + + if (params.area) { + customParams.area = params.area; + } + + if (params.network) { + customParams.network = params.network; + } + + // Custom parameters + if (params.customParams && typeof params.customParams === 'object') { + Object.assign(customParams, params.customParams); + } + + const pageContext = params.pageContext; + if (pageContext) { + // Document URL override + if (pageContext.du) { + customParams.du = pageContext.du; + } + + // Referrer override + if (pageContext.dr) { + customParams.dr = pageContext.dr; + } + + // Document virtual address + if (pageContext.dv) { + customParams.DV = pageContext.dv; + } + + // Keywords + const keywords = getAllOrtbKeywords( + bidderRequest?.ortb2, + pageContext.keyWords, + ); + if (keywords.length > 0) { + customParams.kwrd = keywords.join('+'); + } + + // Local capping + if (pageContext.capping) { + customParams.local_capping = pageContext.capping; + } + + // Key values + if (pageContext.keyValues && typeof pageContext.keyValues === 'object') { + Object.entries(pageContext.keyValues).forEach(([key, value]) => { + keyValues[`kv${key}`] = value; + }); + } + } + + const du = customParams.du || deepAccess(bidderRequest, 'refererInfo.page'); + const dr = customParams.dr || deepAccess(bidderRequest, 'refererInfo.ref'); + + if (du) customParams.du = du; + if (dr) customParams.dr = dr; + + const dsaRequired = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa.required'); + if (dsaRequired !== undefined) { + customParams.dsainfo = dsaRequired; + } + + return { + customParams, + keyValues, + }; +} + +function buildUserIds(customParams) { + const userIds = {}; + if (customParams.lu) { + userIds.lu = customParams.lu; + } + if (customParams.aid) { + userIds.aid = customParams.aid; + } + return userIds; +} + +function getNpaFromPubConsent(pubConsent) { + const params = new URLSearchParams(pubConsent); + return params.get('npa') === '1'; +} + +function buildOpenRTBRequest(bidRequests, bidderRequest) { + const { customParams, keyValues } = parseParams( + bidRequests[0].params, + bidderRequest, + ); + const imp = bidRequests.map((bid) => { + const sizes = getAdUnitSizes(bid); + const imp = { + id: bid.bidId, + tagid: bid.params.slot, + secure: 1, + }; + if (bid.params.slotSequence) { + imp.ext = { + pos: String(bid.params.slotSequence) + } + } + + if (bid.mediaTypes?.banner) { + imp.banner = { + format: sizes.map((size) => ({ + w: size[0], + h: size[1], + })), + }; + } + if (bid.mediaTypes?.native) { + imp.native = { + request: '{}', + ver: '1.2', + }; + } + + return imp; + }); + + const request = { + id: bidderRequest.bidderRequestId, + imp, + site: { + ...bidderRequest.ortb2?.site, + id: customParams.site, + page: customParams.du, + ref: customParams.dr, + ext: { + ...bidderRequest.ortb2?.site?.ext, + area: customParams.area, + kwrd: customParams.kwrd, + dv: customParams.DV + }, + }, + user: { + ext: { + ids: buildUserIds(customParams), + }, + }, + ext: { + network: customParams.network, + keyvalues: keyValues, + }, + at: 1, + tmax: bidderRequest.timeout + }; + + if (customParams.adbeta) { + request.ext.adbeta = customParams.adbeta; + } + + if (bidderRequest.device) { + request.device = bidderRequest.device; + } + + if (bidderRequest.gdprConsent) { + request.user = { + ext: { + npa: getNpaFromPubConsent(customParams.pubconsent), + localcapping: customParams.local_capping, + localadpproduts: customParams.adp_products, + ...request.user.ext, + }, + }; + request.regs = { + gpp: bidderRequest.gdprConsent.consentString, + gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + ext: { + dsa: customParams.dsainfo, + }, + } + } + + return request; +} + +function prepareNativeMarkup(bid) { + const parsedNativeMarkup = safeJSONParse(bid.adm) + const ad = { + data: parsedNativeMarkup || {}, + ems_link: bid.ext?.ems_link || '', + }; + const nativeResponse = parseNativeResponse(ad) || {}; + return nativeResponse; +} + +function interpretResponse(serverResponse) { + const bidResponses = []; + const response = serverResponse.body; + + if (!response || !response.seatbid || !response.seatbid.length) { + return bidResponses; + } + + response.seatbid.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + const bidResponse = { + requestId: bid.impid, + cpm: bid.price, + currency: response.cur || 'USD', + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + netRevenue: true, + dealId: bid.dealid || null, + actgMatch: bid.ext?.actgMatch || 0, + ttl: 300, + meta: { + advertiserDomains: bid.adomain || [], + }, + }; + + if (bid.mtype === 1) { + bidResponse.mediaType = BANNER; + bidResponse.ad = bid.adm; + } else if (bid.mtype === 4) { + bidResponse.mediaType = NATIVE; + bidResponse.native = prepareNativeMarkup(bid); + delete bidResponse.ad; + } + bidResponses.push(bidResponse); + }); + }); + + return bidResponses; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['ringieraxelspringer'], + supportedMediaTypes: [BANNER, NATIVE], + + isBidRequestValid: function (bid) { + if (!bid || !bid.params) { + return false; + } + return !!( + bid.params?.network && + bid.params?.site && + bid.params?.area && + bid.params?.slot + ); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const data = buildOpenRTBRequest(validBidRequests, bidderRequest); + const jsonData = JSON.stringify(data); + const baseUrl = getEndpoint(data.ext.network); + const fullUrl = `${baseUrl}?data=${encodeURIComponent(jsonData)}`; + + // adbeta needs credentials omitted to avoid CORS issues, especially in Firefox + const useCredentials = !data.ext?.adbeta; + + // Switch to POST if URL exceeds 8k characters + if (fullUrl.length > 8192) { + return { + method: 'POST', + url: baseUrl, + data: jsonData, + options: { + withCredentials: useCredentials, + crossOrigin: true, + customHeaders: { + 'Content-Type': 'text/plain' + } + }, + }; + } + + return { + method: 'GET', + url: fullUrl, + options: { + withCredentials: useCredentials, + crossOrigin: true, + }, + }; + }, + + interpretResponse: function (serverResponse) { + return interpretResponse(serverResponse); + }, +}; + +registerBidder(spec); diff --git a/modules/dasBidAdapter.md b/modules/dasBidAdapter.md new file mode 100644 index 00000000000..2b28415df9b --- /dev/null +++ b/modules/dasBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +``` +Module Name: DAS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@ringpublishing.com +``` + +# Description + +Module that connects to DAS demand sources. +Only banner and native format is supported. + +# Test Parameters +```js +var adUnits = [{ + code: 'test-div-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'das', + params: { + network: '4178463', + site: 'test', + area: 'areatest', + slot: 'slot' + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Type | Description | Example | +|------------------------------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| network | required | String | Specific identifier provided by DAS | `"4178463"` | +| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit. Represents the website/domain in the ad unit hierarchy | `"example_com"` | +| area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed. Represents the content section or category | `"sport"` | +| slot | required | String | Ad unit placement name (case-insensitive) | `"slot"` | +| slotSequence | optional | Number | Ad unit sequence position provided by DAS | `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"` | +| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"` | +| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]` | +| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` | +| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` | +| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` | +| customParams | optional | Object | Custom request params | `{}` | diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 60ad1ffbcea..25ded85bf05 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,4 +1,5 @@ -import {deepAccess, getWinDimensions, getWindowTop, isEmpty, isGptPubadsDefined} from '../src/utils.js'; +import {getDevicePixelRatio} from '../libraries/devicePixelRatio/devicePixelRatio.js'; +import {deepAccess, getWinDimensions, getWindowTop, isGptPubadsDefined} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; @@ -6,92 +7,11 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; -import {isWebdriverEnabled} from '../libraries/webdriver/webdriver.js'; +import {isWebdriverEnabled, isSeleniumDetected} from '../libraries/webdriver/webdriver.js'; +import { buildNativeRequest, parseNativeResponse } from '../libraries/nativeAssetsUtils.js'; export const storage = getStorageManager({bidderCode: 'datablocks'}); -const NATIVE_ID_MAP = {}; -const NATIVE_PARAMS = { - title: { - id: 1, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - cta: { - id: 6, - type: 12, - name: 'data' - }, - body2: { - id: 7, - name: 'data', - type: 10 - }, - rating: { - id: 8, - name: 'data', - type: 3 - }, - likes: { - id: 9, - name: 'data', - type: 4 - }, - downloads: { - id: 10, - name: 'data', - type: 5 - }, - displayUrl: { - id: 11, - name: 'data', - type: 11 - }, - price: { - id: 12, - name: 'data', - type: 6 - }, - salePrice: { - id: 13, - name: 'data', - type: 7 - }, - address: { - id: 14, - name: 'data', - type: 9 - }, - phone: { - id: 15, - name: 'data', - type: 8 - } -}; - -Object.keys(NATIVE_PARAMS).forEach((key) => { - NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key; -}); - // DEFINE THE PREBID BIDDER SPEC export const spec = { supportedMediaTypes: [BANNER, NATIVE], @@ -208,15 +128,15 @@ export const spec = { return { 'wiw': windowDimensions.innerWidth, 'wih': windowDimensions.innerHeight, - 'saw': windowDimensions.screen.availWidth, - 'sah': windowDimensions.screen.availHeight, - 'scd': windowDimensions.screen.colorDepth, + 'saw': null, + 'sah': null, + 'scd': null, 'sw': windowDimensions.screen.width, 'sh': windowDimensions.screen.height, 'whl': win.history.length, 'wxo': win.pageXOffset, 'wyo': win.pageYOffset, - 'wpr': win.devicePixelRatio, + 'wpr': getDevicePixelRatio(win), 'is_bot': botTest.doTests(), 'is_hid': win.document.hidden, 'vs': win.document.visibilityState @@ -266,46 +186,6 @@ export const spec = { return []; } - // CONVERT PREBID NATIVE REQUEST OBJ INTO RTB OBJ - function createNativeRequest(bid) { - const assets = []; - if (bid.nativeParams) { - Object.keys(bid.nativeParams).forEach((key) => { - if (NATIVE_PARAMS[key]) { - const {name, type, id} = NATIVE_PARAMS[key]; - const assetObj = type ? {type} : {}; - let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key]; - if (len) { - assetObj.len = len; - } - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - const wmin = aRatios.min_width || 0; - const hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - assetObj.wmin = wmin; - assetObj.hmin = hmin; - } - if (sizes && sizes.length) { - sizes = [].concat(...sizes); - assetObj.w = sizes[0]; - assetObj.h = sizes[1]; - } - const asset = {required: required ? 1 : 0, id}; - asset[name] = assetObj; - assets.push(asset); - } - }); - } - return { - ver: '1.2', - request: { - assets: assets, - context: 1, - plcmttype: 1, - ver: '1.2' - } - } - } const imps = []; // ITERATE THE VALID REQUESTS AND GENERATE IMP OBJECT validRequests.forEach(bidRequest => { @@ -343,7 +223,7 @@ export const spec = { } } else if (deepAccess(bidRequest, `mediaTypes.native`)) { // ADD TO THE LIST OF IMP REQUESTS - imp.native = createNativeRequest(bidRequest); + imp.native = buildNativeRequest(bidRequest.nativeParams); imps.push(imp); } }); @@ -517,37 +397,6 @@ export const spec = { // PARSE THE RTB RESPONSE AND RETURN FINAL RESULTS interpretResponse: function(rtbResponse, bidRequest) { - // CONVERT NATIVE RTB RESPONSE INTO PREBID RESPONSE - function parseNative(native) { - const {assets, link, imptrackers, jstracker} = native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || [], - impressionTrackers: imptrackers || [], - javascriptTrackers: jstracker ? [jstracker] : [] - }; - - (assets || []).forEach((asset) => { - const {id, img, data, title} = asset; - const key = NATIVE_ID_MAP[id]; - if (key) { - if (!isEmpty(title)) { - result.title = title.text - } else if (!isEmpty(img)) { - result[key] = { - url: img.url, - height: img.h, - width: img.w - } - } else if (!isEmpty(data)) { - result[key] = data.value; - } - } - }); - - return result; - } - const bids = []; const resBids = deepAccess(rtbResponse, 'body.seatbid') || []; resBids.forEach(bid => { @@ -561,7 +410,7 @@ export const spec = { case 'native': const nativeResult = JSON.parse(bid.adm); - bids.push(Object.assign({}, resultItem, {mediaType: NATIVE, native: parseNative(nativeResult.native)})); + bids.push(Object.assign({}, resultItem, {mediaType: NATIVE, native: parseNativeResponse(nativeResult.native)})); break; default: @@ -583,37 +432,7 @@ export class BotClientTests { }, selenium: function () { - let response = false; - - if (window && document) { - const results = [ - 'webdriver' in window, - '_Selenium_IDE_Recorder' in window, - 'callSelenium' in window, - '_selenium' in window, - '__webdriver_script_fn' in document, - '__driver_evaluate' in document, - '__webdriver_evaluate' in document, - '__selenium_evaluate' in document, - '__fxdriver_evaluate' in document, - '__driver_unwrapped' in document, - '__webdriver_unwrapped' in document, - '__selenium_unwrapped' in document, - '__fxdriver_unwrapped' in document, - '__webdriver_script_func' in document, - document.documentElement.getAttribute('selenium') !== null, - document.documentElement.getAttribute('webdriver') !== null, - document.documentElement.getAttribute('driver') !== null - ]; - - results.forEach(result => { - if (result === true) { - response = true; - } - }) - } - - return response; + return isSeleniumDetected(window, document); }, } } diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index 78d5c143f55..69ff56621fd 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -5,6 +5,7 @@ import {Renderer} from '../src/Renderer.js'; import {logWarn} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; const ADAPTER_VERSION = '1.1.0'; const BIDDER_CODE = 'displayio'; @@ -70,7 +71,7 @@ export const spec = { }; function getPayload (bid, bidderRequest) { - const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; + const connection = getConnectionInfo(); const storage = getStorageManager({bidderCode: BIDDER_CODE}); const userSession = (() => { let us = storage.getDataFromLocalStorage(US_KEY); @@ -133,7 +134,7 @@ function getPayload (bid, bidderRequest) { device: { w: window.screen.width, h: window.screen.height, - connection_type: connection ? connection.effectiveType : '', + connection_type: connection?.effectiveType || '', } } } diff --git a/modules/dpaiBidAdapter.js b/modules/dpaiBidAdapter.js new file mode 100644 index 00000000000..4cf359b196b --- /dev/null +++ b/modules/dpaiBidAdapter.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 = 'dpai'; +const AD_URL = 'https://ssp.drift-pixel.ai/pbjs'; +const SYNC_URL = 'https://sync.drift-pixel.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/dpaiBidAdapter.md b/modules/dpaiBidAdapter.md new file mode 100644 index 00000000000..4882abdacc4 --- /dev/null +++ b/modules/dpaiBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: DPAI Bidder Adapter +Module Type: DPAI Bidder Adapter +Maintainer: adops@driftpixel.ai +``` + +# Description + +Connects to DPAI exchange for bids. +DPAI 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: 'dpai', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'dpai', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'dpai', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/escalaxBidAdapter.js b/modules/escalaxBidAdapter.js index 027e41d7c56..cf8997105b5 100644 --- a/modules/escalaxBidAdapter.js +++ b/modules/escalaxBidAdapter.js @@ -2,6 +2,7 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { getTimeZone } from '../libraries/timezone/timezone.js'; const BIDDER_CODE = 'escalax'; const ESCALAX_SOURCE_ID_MACRO = '[sourceId]'; @@ -52,8 +53,7 @@ function getSubdomain() { }; try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; + const region = getTimeZone().split('/')[0]; return regionMap[region] || 'bidder_us'; } catch (err) { return 'bidder_us'; diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index 56079a0a652..b345bf3b9af 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; import {getBidIdParameter, logInfo, mergeDeep} from '../src/utils.js'; +import { getTimeZone } from '../libraries/timezone/timezone.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -303,8 +304,7 @@ function getUserSyncUrlByRegion() { */ function getRegionSubdomainSuffix() { try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; + const region = getTimeZone().split('/')[0]; switch (region) { case 'Europe': diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index 95fd67c779b..d1e8048be85 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -18,7 +18,7 @@ const AD_TYPES = [4309, 641]; const DTX_TYPES = [5061]; const TARGET_NAME = 'inline'; const BIDDER_CODE = 'flipp'; -const ENDPOINT = 'https://gateflipp.flippback.com/flyer-locator-service/client_bidding'; +const ENDPOINT = 'https://ads-flipp.com/flyer-locator-service/client_bidding'; const DEFAULT_TTL = 30; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_CREATIVE_TYPE = 'NativeX'; diff --git a/modules/floxisBidAdapter.js b/modules/floxisBidAdapter.js new file mode 100644 index 00000000000..c677f054a46 --- /dev/null +++ b/modules/floxisBidAdapter.js @@ -0,0 +1,149 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { triggerPixel, mergeDeep } from '../src/utils.js'; + +const BIDDER_CODE = 'floxis'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_REGION = 'us-e'; +const DEFAULT_PARTNER = BIDDER_CODE; +const PARTNER_REGION_WHITELIST = { + [DEFAULT_PARTNER]: [DEFAULT_REGION], +}; + +function isAllowedPartnerRegion(partner, region) { + return PARTNER_REGION_WHITELIST[partner]?.includes(region) || false; +} + +function getEndpointUrl(seat, region, partner) { + if (!isAllowedPartnerRegion(partner, region)) return null; + const host = partner === BIDDER_CODE + ? `${region}.floxis.tech` + : `${partner}-${region}.floxis.tech`; + return `https://${host}/pbjs?seat=${encodeURIComponent(seat)}`; +} + +function normalizeBidParams(params = {}) { + return { + seat: params.seat, + region: params.region ?? DEFAULT_REGION, + partner: params.partner ?? DEFAULT_PARTNER + }; +} + +const CONVERTER = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; + + let floorInfo; + if (typeof bidRequest.getFloor === 'function') { + try { + floorInfo = bidRequest.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*' + }); + } catch (e) { } + } + const floor = floorInfo?.floor; + const floorCur = floorInfo?.currency || DEFAULT_CURRENCY; + if (typeof floor === 'number' && !isNaN(floor)) { + imp.bidfloor = floor; + imp.bidfloorcur = floorCur; + } + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + prebid: { + adapter: BIDDER_CODE, + version: '$prebid.version$' + } + } + }); + return req; + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid(bid) { + const params = bid?.params; + if (!params) return false; + const { seat, region, partner } = normalizeBidParams(params); + if (typeof seat !== 'string' || !seat.length) return false; + if (!isAllowedPartnerRegion(partner, region)) return false; + return true; + }, + + buildRequests(validBidRequests = [], bidderRequest = {}) { + if (!validBidRequests.length) return []; + const filteredBidRequests = validBidRequests.filter((bidRequest) => spec.isBidRequestValid(bidRequest)); + if (!filteredBidRequests.length) return []; + + const bidRequestsByParams = filteredBidRequests.reduce((groups, bidRequest) => { + const { seat, region, partner } = normalizeBidParams(bidRequest.params); + const key = `${seat}|${region}|${partner}`; + groups[key] = groups[key] || []; + groups[key].push({ + ...bidRequest, + params: { + ...bidRequest.params, + seat, + region, + partner + } + }); + return groups; + }, {}); + + return Object.values(bidRequestsByParams).map((groupedBidRequests) => { + const { seat, region, partner } = groupedBidRequests[0].params; + const url = getEndpointUrl(seat, region, partner); + if (!url) return null; + return { + method: 'POST', + url, + data: CONVERTER.toORTB({ bidRequests: groupedBidRequests, bidderRequest }), + options: { + withCredentials: true, + contentType: 'text/plain' + } + }; + }).filter(Boolean); + }, + + interpretResponse(response, request) { + if (!response?.body || !request?.data) return []; + return CONVERTER.fromORTB({ request: request.data, response: response.body })?.bids || []; + }, + + getUserSyncs() { + return []; + }, + + onBidWon(bid) { + if (bid.burl) { + triggerPixel(bid.burl); + } + if (bid.nurl) { + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); diff --git a/modules/floxisBidAdapter.md b/modules/floxisBidAdapter.md new file mode 100644 index 00000000000..f36db0c6577 --- /dev/null +++ b/modules/floxisBidAdapter.md @@ -0,0 +1,59 @@ +# Overview + +``` +Module Name: Floxis Bidder Adapter +Module Type: Bidder Adapter +Maintainer: admin@floxis.tech +``` + +# Description + +The Floxis Bid Adapter enables integration with the Floxis programmatic advertising platform via Prebid.js. It supports banner, video (instream and outstream), and native formats. + +**Key Features:** +- Banner, Video and Native ad support +- OpenRTB 2.x compliant +- Privacy regulation compliance (GDPR, USP, GPP, COPPA) +- Prebid.js Floors Module support + +## Supported Media Types +- Banner +- Video +- Native + +## Floors Module Support +The Floxis Bid Adapter supports the Prebid.js [Floors Module](https://docs.prebid.org/dev-docs/modules/floors.html). Floor values are automatically included in the OpenRTB request as `imp.bidfloor` and `imp.bidfloorcur`. + +## Privacy +Privacy fields (GDPR, USP, GPP, COPPA) are handled by Prebid.js core and automatically included in the OpenRTB request. + +## Example Usage +```javascript +pbjs.addAdUnits([ + { + code: 'adunit-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + bids: [{ + bidder: 'floxis', + params: { + seat: 'testSeat', + region: 'us-e', + partner: 'floxis' + } + }] + } +]); +``` + +# Configuration + +## Parameters + +| Name | Scope | Description | Example | Type | +| --- | --- | --- | --- | --- | +| `seat` | required | Seat identifier | `'testSeat'` | `string` | +| `region` | required | Region identifier for routing | `'us-e'` | `string` | +| `partner` | required | Partner identifier | `'floxis'` | `string` | + +## Testing +Unit tests are provided in `test/spec/modules/floxisBidAdapter_spec.js` and cover validation, request building, response interpretation, and bid-won notifications. diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index a500e06941c..e1be0488885 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -1,4 +1,4 @@ -import { _each, deepAccess, deepSetValue, isEmpty } from '../src/utils.js'; +import { _each, deepAccess, deepSetValue, isEmpty, isFn, isPlainObject } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -10,9 +10,83 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'fluct'; const END_POINT = 'https://hb.adingo.jp/prebid'; -const VERSION = '1.2'; +const VERSION = '1.3'; const NET_REVENUE = true; const TTL = 300; +const DEFAULT_CURRENCY = 'JPY'; + +/** + * Get bid floor price for a specific size + * @param {BidRequest} bid + * @param {Array} size - [width, height] + * @returns {{floor: number, currency: string}|null} floor price data + */ +function getBidFloorForSize(bid, size) { + if (!isFn(bid.getFloor)) { + return null; + } + + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: size + }); + + if (isPlainObject(floorInfo) && !isNaN(floorInfo.floor) && floorInfo.currency === DEFAULT_CURRENCY) { + return { + floor: floorInfo.floor, + currency: floorInfo.currency + }; + } + + return null; +} + +/** + * Get the highest bid floor price across all sizes + * @param {BidRequest} bid + * @returns {{floor: number, currency: string}|null} floor price data + */ +function getHighestBidFloor(bid) { + const sizes = bid.sizes || []; + let highestFloor = 0; + let floorCurrency = DEFAULT_CURRENCY; + + if (sizes.length > 0) { + sizes.forEach(size => { + const floorData = getBidFloorForSize(bid, size); + if (floorData && floorData.floor > highestFloor) { + highestFloor = floorData.floor; + floorCurrency = floorData.currency; + } + }); + + if (highestFloor > 0) { + return { + floor: highestFloor, + currency: floorCurrency + }; + } + } + + // Final fallback: use params.bidfloor if available + if (bid.params.bidfloor) { + // Check currency if specified - only JPY is supported + if (bid.params.currency && bid.params.currency !== DEFAULT_CURRENCY) { + return null; + } + const floorValue = parseFloat(bid.params.bidfloor); + if (isNaN(floorValue)) { + return null; + } + return { + floor: floorValue, + currency: DEFAULT_CURRENCY + }; + } + + return null; +} export const spec = { code: BIDDER_CODE, @@ -88,10 +162,20 @@ export const spec = { } data.sizes = []; _each(request.sizes, (size) => { - data.sizes.push({ + const sizeObj = { w: size[0], h: size[1] - }); + }; + + // Get floor price for this specific size + const floorData = getBidFloorForSize(request, size); + if (floorData) { + sizeObj.ext = { + floor: floorData.floor + }; + } + + data.sizes.push(sizeObj); }); data.params = request.params; @@ -103,6 +187,13 @@ export const spec = { data.instl = deepAccess(request, 'ortb2Imp.instl') === 1 || request.params.instl === 1 ? 1 : 0; + // Set top-level bidfloor to the highest floor across all sizes + const highestFloorData = getHighestBidFloor(request); + if (highestFloorData) { + data.bidfloor = highestFloorData.floor; + data.bidfloorcur = highestFloorData.currency; + } + const searchParams = new URLSearchParams({ dfpUnitCode: request.params.dfpUnitCode, tagId: request.params.tagId, diff --git a/modules/fwsspBidAdapter.js b/modules/fwsspBidAdapter.js index 3a07402eb44..f64c21e71c4 100644 --- a/modules/fwsspBidAdapter.js +++ b/modules/fwsspBidAdapter.js @@ -110,7 +110,7 @@ export const spec = { if (bidderRequest && bidderRequest.gdprConsent) { keyValues._fw_gdpr_consent = bidderRequest.gdprConsent.consentString; if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - keyValues._fw_gdpr = bidderRequest.gdprConsent.gdprApplies; + keyValues._fw_gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } } diff --git a/modules/gamAdServerVideo.js b/modules/gamAdServerVideo.js index d5541c16424..b97f4ff598c 100644 --- a/modules/gamAdServerVideo.js +++ b/modules/gamAdServerVideo.js @@ -28,6 +28,7 @@ import { fetch } from '../src/ajax.js'; import XMLUtil from '../libraries/xmlUtils/xmlUtils.js'; import {getGlobalVarName} from '../src/buildOptions.js'; +import { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; /** * @typedef {Object} DfpVideoParams * @@ -292,7 +293,35 @@ async function getVastForLocallyCachedBids(gamVastWrapper, localCacheMap) { }; export async function getVastXml(options, localCacheMap = vastLocalCache) { - const vastUrl = buildGamVideoUrl(options); + let vastUrl = buildGamVideoUrl(options); + + const adUnit = options.adUnit; + const video = adUnit?.mediaTypes?.video; + const sdkApis = (video?.api || []).join(','); + const usPrivacy = uspDataHandler.getConsentData?.(); + const gpp = gppDataHandler.getConsentData?.(); + // Adding parameters required by ima + if (config.getConfig('cache.useLocal') && window.google?.ima) { + vastUrl = new URL(vastUrl); + const imaSdkVersion = `h.${window.google.ima.VERSION}`; + vastUrl.searchParams.set('omid_p', `Google1/${imaSdkVersion}`); + vastUrl.searchParams.set('sdkv', imaSdkVersion); + if (sdkApis) { + vastUrl.searchParams.set('sdk_apis', sdkApis); + } + if (usPrivacy) { + vastUrl.searchParams.set('us_privacy', usPrivacy); + } else if (gpp) { + // Extract an usPrivacy string from the GPP string if possible + const uspFromGpp = retrieveUspInfoFromGpp(gpp); + if (uspFromGpp) { + vastUrl.searchParams.set('us_privacy', uspFromGpp) + } + } + + vastUrl = vastUrl.toString(); + } + const response = await fetch(vastUrl); if (!response.ok) { throw new Error('Unable to fetch GAM VAST wrapper'); @@ -307,6 +336,41 @@ export async function getVastXml(options, localCacheMap = vastLocalCache) { return gamVastWrapper; } +/** + * Extract a US Privacy string from the GPP data + */ +function retrieveUspInfoFromGpp(gpp) { + if (!gpp) { + return undefined; + } + const parsedSections = gpp.gppData?.parsedSections; + if (parsedSections) { + if (parsedSections.uspv1) { + const usp = parsedSections.uspv1; + return `${usp.Version}${usp.Notice}${usp.OptOutSale}${usp.LspaCovered}` + } else { + let saleOptOut; + let saleOptOutNotice; + Object.values(parsedSections).forEach(parsedSection => { + (Array.isArray(parsedSection) ? parsedSection : [parsedSection]).forEach(ps => { + const sectionSaleOptOut = ps.SaleOptOut; + const sectionSaleOptOutNotice = ps.SaleOptOutNotice; + if (saleOptOut === undefined && saleOptOutNotice === undefined && sectionSaleOptOut != null && sectionSaleOptOutNotice != null) { + saleOptOut = sectionSaleOptOut; + saleOptOutNotice = sectionSaleOptOutNotice; + } + }); + }); + if (saleOptOut !== undefined && saleOptOutNotice !== undefined) { + const uspOptOutSale = saleOptOut === 0 ? '-' : saleOptOut === 1 ? 'Y' : 'N'; + const uspOptOutNotice = saleOptOutNotice === 0 ? '-' : saleOptOutNotice === 1 ? 'Y' : 'N'; + const uspLspa = uspOptOutSale === '-' && uspOptOutNotice === '-' ? '-' : 'Y'; + return `1${uspOptOutNotice}${uspOptOutSale}${uspLspa}`; + } + } + } + return undefined +} export async function getBase64BlobContent(blobUrl) { const response = await fetch(blobUrl); diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index 640a871e654..a260dfa6ed7 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -1,3 +1,4 @@ +import { getTimeZone } from '../libraries/timezone/timezone.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; /** @@ -81,8 +82,7 @@ function getAdUrlByRegion(bid) { ENDPOINT = ENDPOINTS[bid.params.region]; } else { try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; + const region = getTimeZone().split('/')[0]; switch (region) { case 'Europe': diff --git a/modules/greenbidsBidAdapter.js b/modules/greenbidsBidAdapter.js index 2b5a0790459..58aa8608194 100644 --- a/modules/greenbidsBidAdapter.js +++ b/modules/greenbidsBidAdapter.js @@ -1,4 +1,5 @@ import { getValue, logError, deepAccess, parseSizesInput, getBidIdParameter, logInfo, getWinDimensions, getScreenOrientation } from '../src/utils.js'; +import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { getHLen } from '../libraries/navigatorData/navigatorData.js'; @@ -10,7 +11,7 @@ import { getReferrerInfo, getPageTitle, getPageDescription, getConnectionDownLin */ const BIDDER_CODE = 'greenbids'; -const ENDPOINT_URL = 'https://hb.greenbids.ai'; +export const ENDPOINT_URL = 'https://hb.greenbids.ai'; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { @@ -65,7 +66,7 @@ export const spec = { device: bidderRequest?.ortb2?.device || {}, deviceWidth: screen.width, deviceHeight: screen.height, - devicePixelRatio: topWindow.devicePixelRatio, + devicePixelRatio: getDevicePixelRatio(topWindow), screenOrientation: getScreenOrientation(), historyLength: getHLen(), viewportHeight: getWinDimensions().visualViewport.height, diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 46989610baf..e894154e6d1 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -47,13 +47,6 @@ const LOG_ERROR_MESS = { }; const ALIAS_CONFIG = { - 'trustx': { - endpoint: 'https://grid.bidswitch.net/hbjson?sp=trustx', - syncurl: 'https://x.bidswitch.net/sync?ssp=themediagrid', - bidResponseExternal: { - netRevenue: false - } - }, 'gridNM': { defaultParams: { multiRequest: true @@ -66,7 +59,7 @@ let hasSynced = false; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['playwire', 'adlivetech', 'gridNM', { code: 'trustx', skipPbsAliasing: true }], + aliases: ['playwire', 'adlivetech', 'gridNM'], supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 8bfb5b841d0..1b8cc95e2dc 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -2,8 +2,9 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {_each, deepAccess, getWinDimensions, logError, logWarn, parseSizesInput} from '../src/utils.js'; import {config} from '../src/config.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import {getDevicePixelRatio} from '../libraries/devicePixelRatio/devicePixelRatio.js'; import {getStorageManager} from '../src/storageManager.js'; - import {registerBidder} from '../src/adapters/bidderFactory.js'; /** @@ -42,8 +43,8 @@ function _getBrowserParams(topWindowUrl, mosttopLocation) { let ns; function getNetworkSpeed () { - const connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection); - const Mbps = connection && (connection.downlink || connection.bandwidth); + const connection = getConnectionInfo(); + const Mbps = connection?.downlink ?? connection?.bandwidth; return Mbps ? Math.round(Mbps * 1024) : null; } @@ -89,7 +90,7 @@ function _getBrowserParams(topWindowUrl, mosttopLocation) { pu: stripGGParams(topUrl), tpl: mosttopURL, ce: storage.cookiesAreEnabled(), - dpr: topWindow.devicePixelRatio || 1, + dpr: getDevicePixelRatio(topWindow), jcsi: JSON.stringify(JCSI), ogu: getOgURL() }; @@ -322,28 +323,53 @@ function getGreatestDimensions(sizes) { return [maxw, maxh]; } -function getEids(userId) { - const idProperties = [ - 'uid', - 'eid', - 'lipbid', - 'envelope', - 'id' - ]; - - return Object.keys(userId).reduce(function (eids, provider) { - const eid = userId[provider]; - switch (typeof eid) { - case 'string': - eids[provider] = eid; - break; - - case 'object': - const idProp = idProperties.filter(prop => eid.hasOwnProperty(prop)); - idProp.length && (eids[provider] = eid[idProp[0]]); - break; +function getFirstUid(eid) { + if (!eid || !Array.isArray(eid.uids)) return null; + return eid.uids.find(uid => uid && uid.id); +} + +function getUserEids(bidRequest, bidderRequest) { + const bidderRequestEids = deepAccess(bidderRequest, 'ortb2.user.ext.eids'); + if (Array.isArray(bidderRequestEids) && bidderRequestEids.length) { + return bidderRequestEids; + } + const bidEids = deepAccess(bidRequest, 'userIdAsEids'); + if (Array.isArray(bidEids) && bidEids.length) { + return bidEids; + } + const bidUserEids = deepAccess(bidRequest, 'user.ext.eids'); + if (Array.isArray(bidUserEids) && bidUserEids.length) { + return bidUserEids; + } + return []; +} + +function isPubProvidedIdEid(eid) { + const source = (eid && eid.source) ? eid.source.toLowerCase() : ''; + if (!source || !pubProvidedIdSources.includes(source) || !Array.isArray(eid.uids)) return false; + return eid.uids.some(uid => uid && uid.ext && uid.ext.stype); +} + +function getEidsFromEidsArray(eids) { + return (Array.isArray(eids) ? eids : []).reduce((ids, eid) => { + const source = (eid.source || '').toLowerCase(); + if (source === 'uidapi.com') { + const uid = getFirstUid(eid); + if (uid) { + ids.uid2 = uid.id; + } + } else if (source === 'liveramp.com') { + const uid = getFirstUid(eid); + if (uid) { + ids.idl_env = uid.id; + } + } else if (source === 'adserver.org' && Array.isArray(eid.uids)) { + const tdidUid = eid.uids.find(uid => uid && uid.id && uid.ext && uid.ext.rtiPartner === 'TDID'); + if (tdidUid) { + ids.tdid = tdidUid.id; + } } - return eids; + return ids; }, {}); } @@ -367,12 +393,12 @@ function buildRequests(validBidRequests, bidderRequest) { bidId, mediaTypes = {}, params = {}, - userId = {}, ortb2Imp, adUnitCode = '' } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); - const eids = getEids(userId); + const userEids = getUserEids(bidRequest, bidderRequest); + const eids = getEidsFromEidsArray(userEids); const gpid = deepAccess(ortb2Imp, 'ext.gpid'); const paapiEligible = deepAccess(ortb2Imp, 'ext.ae') === 1 let sizes = [1, 1]; @@ -397,16 +423,20 @@ function buildRequests(validBidRequests, bidderRequest) { } } // Send filtered pubProvidedId's - if (userId && userId.pubProvidedId) { - const filteredData = userId.pubProvidedId.filter(item => pubProvidedIdSources.includes(item.source)); + if (userEids.length) { + const filteredData = userEids.filter(isPubProvidedIdEid); const maxLength = 1800; // replace this with your desired maximum length const truncatedJsonString = jsoStringifynWithMaxLength(filteredData, maxLength); - data.pubProvidedId = truncatedJsonString + if (filteredData.length) { + data.pubProvidedId = truncatedJsonString + } } // ADJS-1286 Read id5 id linktype field - if (userId && userId.id5id && userId.id5id.uid && userId.id5id.ext) { - data.id5Id = userId.id5id.uid || null - data.id5IdLinkType = userId.id5id.ext.linkType || null + const id5Eid = userEids.find(eid => (eid.source || '').toLowerCase() === 'id5-sync.com'); + const id5Uid = getFirstUid(id5Eid); + if (id5Uid && id5Uid.ext) { + data.id5Id = id5Uid.id || null + data.id5IdLinkType = id5Uid.ext.linkType || null } // ADTS-169 add adUnitCode to requests if (adUnitCode) data.aun = adUnitCode; diff --git a/modules/harionBidAdapter.js b/modules/harionBidAdapter.js new file mode 100644 index 00000000000..40b1b8282b3 --- /dev/null +++ b/modules/harionBidAdapter.js @@ -0,0 +1,25 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'harion'; +const GVLID = 1406; +const AD_URL = 'https://east-api.harion-ma.com/pbjs'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse: (serverResponse) => { + if (!serverResponse || !Array.isArray(serverResponse.body)) { + return []; + } + + return interpretResponse(serverResponse); + } +}; + +registerBidder(spec); diff --git a/modules/harionBidAdapter.md b/modules/harionBidAdapter.md new file mode 100644 index 00000000000..a2ec196eeab --- /dev/null +++ b/modules/harionBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Harion Bidder Adapter +Module Type: Harion Bidder Adapter +Maintainer: adtech@markappmedia.site +``` + +# Description + +Connects to Harion exchange for bids. +Harion 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: 'harion', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'harion', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'harion', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/humansecurityRtdProvider.js b/modules/humansecurityRtdProvider.js deleted file mode 100644 index aeb872beb8d..00000000000 --- a/modules/humansecurityRtdProvider.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * This module adds humansecurity provider to the real time data module - * - * The {@link module:modules/realTimeData} module is required - * The module will inject the HUMAN Security script into the context where Prebid.js is initialized, enriching bid requests with specific data to provide advanced protection against ad fraud and spoofing. - * @module modules/humansecurityRtdProvider - * @requires module:modules/realTimeData - */ - -import { submodule } from '../src/hook.js'; -import { - prefixLog, - mergeDeep, - generateUUID, - getWindowSelf, -} from '../src/utils.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; - -/** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule - * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData - */ - -const SUBMODULE_NAME = 'humansecurity'; -const SCRIPT_URL = 'https://sonar.script.ac/prebid/rtd.js'; - -const { logInfo, logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); - -/** @type {string} */ -let clientId = ''; - -/** @type {boolean} */ -let verbose = false; - -/** @type {string} */ -let sessionId = ''; - -/** @type {Object} */ -let hmnsData = { }; - -/** - * Submodule registration - */ -function main() { - submodule('realTimeData', /** @type {RtdSubmodule} */ ({ - name: SUBMODULE_NAME, - - // - init: (config, userConsent) => { - try { - load(config); - return true; - } catch (err) { - logError('init', err.message); - return false; - } - }, - - getBidRequestData: onGetBidRequestData - })); -} - -/** - * Injects HUMAN Security script on the page to facilitate pre-bid signal collection. - * @param {SubmoduleConfig} config - */ -function load(config) { - // By default, this submodule loads the generic implementation script - // only identified by the referrer information. In the future, if publishers - // want to have analytics where their websites are grouped, they can request - // Client ID from HUMAN, pass it here, and it will enable advanced reporting - clientId = config?.params?.clientId || ''; - if (clientId && (typeof clientId !== 'string' || !/^\w{3,16}$/.test(clientId))) { - throw new Error(`The 'clientId' parameter must be a short alphanumeric string`); - } - - // Load/reset the state - verbose = !!config?.params?.verbose; - sessionId = generateUUID(); - hmnsData = {}; - - // We rely on prebid implementation to get the best domain possible here - // In some cases, it still might be null, though - const refDomain = getRefererInfo().domain || ''; - - // Once loaded, the implementation script will publish an API using - // the session ID value it was given in data attributes - const scriptAttrs = { 'data-sid': sessionId }; - const scriptUrl = `${SCRIPT_URL}?r=${refDomain}${clientId ? `&c=${clientId}` : ''}`; - - loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, onImplLoaded, null, scriptAttrs); -} - -/** - * The callback to loadExternalScript - * Establishes the bridge between this RTD submodule and the loaded implementation - */ -function onImplLoaded() { - // We then get a hold on this script using the knowledge of this session ID - const wnd = getWindowSelf(); - const impl = wnd[`sonar_${sessionId}`]; - if (typeof impl !== 'object' || typeof impl.connect !== 'function') { - verbose && logWarn('onload', 'Unable to access the implementation script'); - return; - } - - // And set up a bridge between the RTD submodule and the implementation. - // The first argument is used to identify the caller. - // The callback might be called multiple times to update the signals - // once more precise information is available. - impl.connect(getGlobal(), onImplMessage); -} - -/** - * The bridge function will be called by the implementation script - * to update the token information or report errors - * @param {Object} msg - */ -function onImplMessage(msg) { - if (typeof msg !== 'object') { - return; - } - - switch (msg.type) { - case 'hmns': { - hmnsData = mergeDeep({}, msg.data || {}); - break; - } - case 'error': { - logError('impl', msg.data || ''); - break; - } - case 'warn': { - verbose && logWarn('impl', msg.data || ''); - break; - } - case 'info': { - verbose && logInfo('impl', msg.data || ''); - break; - } - } -} - -/** - * onGetBidRequestData is called once per auction. - * Update the `ortb2Fragments` object with the data from the injected script. - * - * @param {Object} reqBidsConfigObj - * @param {function} callback - * @param {SubmoduleConfig} config - * @param {UserConsentData} userConsent - */ -function onGetBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - // Add device.ext.hmns to the global ORTB data for all vendors to use - // At the time of writing this submodule, "hmns" is an object defined - // internally by humansecurity, and it currently contains "v1" field - // with a token that contains collected signals about this session. - mergeDeep(reqBidsConfigObj.ortb2Fragments.global, { device: { ext: { hmns: hmnsData } } }); - callback(); -} - -/** - * Exporting local (and otherwise encapsulated to this module) functions - * for testing purposes - */ -export const __TEST__ = { - SUBMODULE_NAME, - SCRIPT_URL, - main, - load, - onImplLoaded, - onImplMessage, - onGetBidRequestData -}; - -main(); diff --git a/modules/humansecurityRtdProvider.md b/modules/humansecurityRtdProvider.md index 6722319cbb5..fceb012e27c 100644 --- a/modules/humansecurityRtdProvider.md +++ b/modules/humansecurityRtdProvider.md @@ -23,7 +23,7 @@ sent within bid requests, and used for bot detection on the backend. * 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 +* Does not introduce meaningful latency * 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. @@ -56,8 +56,8 @@ pbjs.setConfig({ }); ``` -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: +Other parameters can also be provided. For example, a client ID obtained from HUMAN can +optionally be provided, or verbose output can be enabled for troubleshooting purposes: ```javascript pbjs.setConfig({ @@ -79,6 +79,7 @@ pbjs.setConfig({ | :--------------- | :------------ | :------------------------------------------------------------------ |:---------| | `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 | +| `perBidderOptOut` | string[] | Pass any bidder alias to opt-out from per-bidder signal generation. | No | ## Logging, latency and troubleshooting @@ -89,9 +90,6 @@ 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: @@ -139,6 +137,7 @@ There are a few points that are worth being mentioned separately, to avoid confu 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. +* The collected signals are encrypted before they are passed to bid adapters and can only be interpreted by HUMAN backend systems. ## Why is this approach an innovation? @@ -199,10 +198,14 @@ ensuring the value of the inventory. ## FAQ +### Is partnership with HUMAN required to use the submodule? + +No. Using this submodule does not require any prior communication with HUMAN or being a client of HUMAN. +It is free and usage of the submodule doesn’t automatically make a Publisher HUMAN client. + ### 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 @@ -211,13 +214,10 @@ 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. +The HUMAN Security RTD submodule seeks to reduce the impacts of signal deprecation without compromising privacy. +Each bid request is enriched with just enough signal to identify if the traffic is invalid or not, and these +signals are encrypted before being included in bid requests to prevent misuse. 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. +consult your attorney when making data access decisions. \ No newline at end of file diff --git a/modules/humansecurityRtdProvider.ts b/modules/humansecurityRtdProvider.ts new file mode 100644 index 00000000000..96b99eadfb7 --- /dev/null +++ b/modules/humansecurityRtdProvider.ts @@ -0,0 +1,204 @@ +/** + * 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, generateUUID, getWindowSelf } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getGlobal, PrebidJS } from '../src/prebidGlobal.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { AllConsentData } from '../src/consentHandler.ts'; +import type { RTDProvider, RTDProviderConfig, RtdProviderSpec } from './rtdModule/spec.ts'; +import type { StartAuctionOptions } from '../src/prebid.ts'; +import type { AuctionProperties } from '../src/auction.ts'; + +declare module './rtdModule/spec.ts' { + interface ProviderConfig { + humansecurity: { + params?: { + clientId?: string; + verbose?: boolean; + perBidderOptOut?: string[]; + }; + }; + } +} + +interface HumanSecurityImpl { + connect( + pbjs: PrebidJS, + callback: (m: string) => void | null, + config: RTDProviderConfig<'humansecurity'> + ): void; + + getBidRequestData( + reqBidsConfigObj: StartAuctionOptions, + callback: () => void, + config: RTDProviderConfig<'humansecurity'>, + userConsent: AllConsentData + ): void; + + onAuctionInitEvent( + pbjs: PrebidJS, + auctionDetails: AuctionProperties, + config: RTDProviderConfig<'humansecurity'>, + userConsent: AllConsentData + ): void; +} + +const SUBMODULE_NAME = 'humansecurity' as const; +const SCRIPT_URL = 'https://sonar.script.ac/prebid/rtd.js'; +const MODULE_VERSION = 1; + +const { logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); + +let implRef: HumanSecurityImpl | null = null; +let clientId: string = ''; +let verbose: boolean = false; +let sessionId: string = ''; + +/** + * Injects HUMAN Security script on the page to facilitate pre-bid signal collection. + */ + +const load = (config: RTDProviderConfig<'humansecurity'>) => { + // Load implementation script and pass configuration parameters via data attributes + 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; + implRef = null; + sessionId = generateUUID(); + + // Get the best domain possible here, it still might be null + 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}` : ''}&mv=${MODULE_VERSION}`; + + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, () => onImplLoaded(config), null, scriptAttrs); +} + +/** + * Retrieves the implementation object created by the loaded script + * using the session ID as a key + */ + +const getImpl = () => { + // Use cached reference if already resolved + if (implRef && typeof implRef === 'object' && typeof implRef.connect === 'function') return implRef; + + // Attempt to resolve from window by session ID + const wnd = getWindowSelf(); + const impl: HumanSecurityImpl = wnd[`sonar_${sessionId}`]; + + if (typeof impl !== 'object' || typeof impl.connect !== 'function') { + verbose && logWarn('onload', 'Unable to access the implementation script'); + return; + } + implRef = impl; + return impl; +} + +/** + * The callback to loadExternalScript + * Establishes the bridge between this RTD submodule and the loaded implementation + */ + +const onImplLoaded = (config: RTDProviderConfig<'humansecurity'>) => { + const impl = getImpl(); + if (!impl) return; + + // And set up a bridge between the RTD submodule and the implementation. + impl.connect(getGlobal(), null, config); +} + +/** + * The bridge function will be called by the implementation script + * to update the token information or report errors + */ + +/** + * https://docs.prebid.org/dev-docs/add-rtd-submodule.html#getbidrequestdata + */ + +const getBidRequestData = ( + reqBidsConfigObj: StartAuctionOptions, + callback: () => void, + config: RtdProviderSpec<'humansecurity'>, + userConsent: AllConsentData +) => { + const impl = getImpl(); + if (!impl || typeof impl.getBidRequestData !== 'function') { + // Implementation not available; continue auction by invoking the callback. + callback(); + return; + } + + impl.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); +} + +/** + * Event hooks + * https://docs.prebid.org/dev-docs/add-rtd-submodule.html#using-event-listeners + */ + +const onAuctionInitEvent = (auctionDetails: AuctionProperties, config: RTDProviderConfig<'humansecurity'>, userConsent: AllConsentData) => { + const impl = getImpl(); + if (!impl || typeof impl.onAuctionInitEvent !== 'function') return; + impl.onAuctionInitEvent(getGlobal(), auctionDetails, config, userConsent); +} + +/** + * Submodule registration + */ + +type RtdProviderSpecWithHooks

= RtdProviderSpec

& { + onAuctionInitEvent?: (auctionDetails: AuctionProperties, config: RTDProviderConfig

, userConsent: AllConsentData) => void; +}; + +const subModule: RtdProviderSpecWithHooks<'humansecurity'> = ({ + name: SUBMODULE_NAME, + init: (config, _userConsent) => { + try { + load(config); + return true; + } catch (err) { + const message = (err && typeof err === 'object' && 'message' in err) + ? (err as any).message + : String(err); + logError('init', message); + return false; + } + }, + getBidRequestData, + onAuctionInitEvent, +}); + +const registerSubModule = () => { submodule('realTimeData', subModule); } +registerSubModule(); + +/** + * Exporting local (and otherwise encapsulated to this module) functions + * for testing purposes + */ + +export const __TEST__ = { + SUBMODULE_NAME, + SCRIPT_URL, + main: registerSubModule, + load, + onImplLoaded, + getBidRequestData +}; diff --git a/modules/hypelabBidAdapter.js b/modules/hypelabBidAdapter.js index 9982af84cc9..bc562b84cb3 100644 --- a/modules/hypelabBidAdapter.js +++ b/modules/hypelabBidAdapter.js @@ -1,6 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { generateUUID, isFn, isPlainObject, getWinDimensions } from '../src/utils.js'; +import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { ajax } from '../src/ajax.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { getWalletPresence, getWalletProviderFlags } from '../libraries/hypelabUtils/hypelabUtils.js'; @@ -40,7 +41,7 @@ function buildRequests(validBidRequests, bidderRequest) { const uuid = uids[0] ? uids[0] : generateTemporaryUUID(); const floor = getBidFloor(request, request.sizes || []); - const dpr = typeof window !== 'undefined' ? window.devicePixelRatio : 1; + const dpr = typeof window !== 'undefined' ? getDevicePixelRatio(window) : 1; const wp = getWalletPresence(); const wpfs = getWalletProviderFlags(); const winDimensions = getWinDimensions(); diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index f1db7890871..dca244bb317 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -2,7 +2,7 @@ import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import {EVENTS} from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; -import {logError, logInfo} from '../src/utils.js'; +import {compressDataWithGZip, isGzipCompressionSupported, logError, logInfo} from '../src/utils.js'; import * as events from '../src/events.js'; const { @@ -12,6 +12,7 @@ const { } = EVENTS const GVLID = 131; +const COMPRESSION_THRESHOLD = 2048; const STANDARD_EVENTS_TO_TRACK = [ AUCTION_END, @@ -44,8 +45,20 @@ const id5Analytics = Object.assign(buildAdapter({analyticsType: 'endpoint'}), { }, sendEvent: (eventToSend) => { - // By giving some content this will be automatically a POST - ajax(id5Analytics.options.ingestUrl, null, JSON.stringify(eventToSend)); + const serializedEvent = JSON.stringify(eventToSend); + if (!id5Analytics.options.compressionDisabled && isGzipCompressionSupported() && serializedEvent.length > COMPRESSION_THRESHOLD) { + compressDataWithGZip(serializedEvent).then(compressedData => { + ajax(id5Analytics.options.ingestUrl, null, compressedData, { + contentType: 'application/json', + customHeaders: { + 'Content-Encoding': 'gzip' + } + }); + }) + } else { + // By giving some content this will be automatically a POST + ajax(id5Analytics.options.ingestUrl, null, serializedEvent); + } }, makeEvent: (event, payload) => { diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index d8f553c6ae0..c3e2e6c31ed 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -8,6 +8,7 @@ import { deepAccess, deepClone, + deepEqual, deepSetValue, isEmpty, isEmptyStr, @@ -118,6 +119,7 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleNam * @property {Array} [segments] - A list of segments to push to partners. Supported only in multiplexing. * @property {boolean} [disableUaHints] - When true, look up of high entropy values through user agent hints is disabled. * @property {string} [gamTargetingPrefix] - When set, the GAM targeting tags will be set and use the specified prefix, for example 'id5'. + * @property {boolean} [exposeTargeting] - When set, the ID5 targeting consumer mechanism will be enabled. */ /** @@ -569,17 +571,30 @@ function incrementNb(cachedObj) { } function updateTargeting(fetchResponse, config) { - if (config.params.gamTargetingPrefix) { - const tags = fetchResponse.tags; - if (tags) { + const tags = fetchResponse.tags; + if (tags) { + if (config.params.gamTargetingPrefix) { window.googletag = window.googletag || {cmd: []}; window.googletag.cmd = window.googletag.cmd || []; window.googletag.cmd.push(() => { for (const tag in tags) { - window.googletag.pubads().setTargeting(config.params.gamTargetingPrefix + '_' + tag, tags[tag]); + window.googletag.setConfig({targeting: {[config.params.gamTargetingPrefix + '_' + tag]: tags[tag]}}); } }); } + + if (config.params.exposeTargeting && !deepEqual(window.id5tags?.tags, tags)) { + window.id5tags = window.id5tags || {cmd: []}; + window.id5tags.cmd = window.id5tags.cmd || []; + window.id5tags.cmd.forEach(tagsCallback => { + setTimeout(() => tagsCallback(tags), 0); + }); + window.id5tags.cmd.push = function (tagsCallback) { + tagsCallback(tags) + Array.prototype.push.call(window.id5tags.cmd, tagsCallback); + }; + window.id5tags.tags = tags + } } } diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 363dd02e831..aaf34fa4054 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -33,7 +33,8 @@ pbjs.setConfig({ }, disableExtensions: false,// optional canCookieSync: true, // optional, has effect only when externalModuleUrl is used - gamTargetingPrefix: "id5" // optional, when set the ID5 module will set gam targeting paramaters with this prefix + gamTargetingPrefix: "id5", // optional, when set the ID5 module will set gam targeting paramaters with this prefix + exposeTargeting: false // optional, when set the ID5 module will execute `window.id5tags.cmd` callbacks for custom targeting tags }, storage: { type: 'html5', // "html5" is the required storage type @@ -47,25 +48,26 @@ pbjs.setConfig({ }); ``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | -| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | -| params | Required | Object | Details for the ID5 ID. | | -| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | -| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js -| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | -| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | -| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | -| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | -| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | -| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | -| params.canCookieSync | Optional | Boolean | Set this to `true` to enable cookie syncing with other ID5 partners. See [our documentation](https://wiki.id5.io/docs/initiate-cookie-sync-to-id5) for details. Default `false` | `true` or `false` | +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------| +| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | +| params | Required | Object | Details for the ID5 ID. | | +| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js +| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | +| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | +| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | +| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | +| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | +| params.canCookieSync | Optional | Boolean | Set this to `true` to enable cookie syncing with other ID5 partners. See [our documentation](https://wiki.id5.io/docs/initiate-cookie-sync-to-id5) for details. Default `false` | `true` or `false` | | params.gamTargetingPrefix | Optional | String | When this parameter is set the ID5 module will set appropriate GAM pubads targeting tags | `id5` | -| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | -| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | -| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | -| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | +| params.exposeTargeting | Optional | Boolean | When this parameter is set the ID5 module will execute `window.id5tags.cmd` callbacks for custom targeting tags | `false` | +| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 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). diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 9819a1587e9..49ffe99245d 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -22,7 +22,6 @@ const DEFAULT_VIDEO_WIDTH = 640; const DEFAULT_VIDEO_HEIGHT = 360; const ORIGIN = 'https://sonic.impactify.media'; const LOGGER_URI = 'https://logger.impactify.media'; -const LOGGER_JS_URI = 'https://log.impactify.it' const AUCTION_URI = '/bidder'; const COOKIE_SYNC_URI = '/static/cookie_sync.html'; const GVL_ID = 606; @@ -387,18 +386,5 @@ export const spec = { return true; }, - - /** - * Register bidder specific code, which will execute if the bid request failed - * @param {*} param0 - */ - onBidderError: function ({ error, bidderRequest }) { - ajax(`${LOGGER_JS_URI}/logger`, null, JSON.stringify({ error, bidderRequest }), { - method: 'POST', - contentType: 'application/json' - }); - - return true; - }, }; registerBidder(spec); diff --git a/modules/incrementxBidAdapter.js b/modules/incrementxBidAdapter.js index e7f8ff51fa9..07eed9efd7f 100644 --- a/modules/incrementxBidAdapter.js +++ b/modules/incrementxBidAdapter.js @@ -14,6 +14,33 @@ const ENDPOINT_URL = 'https://hb.incrementxserv.com/vzhbidder/bid'; const DEFAULT_CURRENCY = 'USD'; const CREATIVE_TTL = 300; +// OUTSTREAM RENDERER +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; +} + export const spec = { code: BIDDER_CODE, aliases: ['incrx'], @@ -26,7 +53,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params.placementId); + return !!(bid.params && bid.params.placementId); }, hasTypeVideo(bid) { return typeof bid.mediaTypes !== 'undefined' && typeof bid.mediaTypes.video !== 'undefined'; @@ -41,12 +68,8 @@ 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; - } + let mdType = bidRequest.mediaTypes[BANNER] ? 1 : 2; + const requestParams = { _vzPlacementId: bidRequest.params.placementId, sizes: sizes, @@ -54,15 +77,19 @@ export const spec = { _rqsrc: bidderRequest.refererInfo.page, mChannel: mdType }; + let payload; - if (mdType === 1) { // BANNER + + if (mdType === 1) { + // BANNER payload = { - q: encodeURI(JSON.stringify(requestParams)) + q: encodeURIComponent(JSON.stringify(requestParams)) }; - } else { // VIDEO or other types + } else { + // VIDEO payload = { - q: encodeURI(JSON.stringify(requestParams)), - bidderRequestData: encodeURI(JSON.stringify(bidderRequest)) + q: encodeURIComponent(JSON.stringify(requestParams)), + bidderRequestData: encodeURIComponent(JSON.stringify(bidderRequest)) }; } @@ -80,19 +107,13 @@ 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, bidderRequest) { + interpretResponse: function (serverResponse, request) { 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 = { + if (isEmpty(response)) return []; + + const ixReq = request.data || {}; + + const bid = { requestId: response.slotBidId, cpm: response.cpm > 0 ? response.cpm : 0, currency: response.currency || DEFAULT_CURRENCY, @@ -107,57 +128,63 @@ export const spec = { meta: { mediaType: response.mediaType, advertiserDomains: response.advertiserDomains || [] - }, - + } }; - 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; + + // BANNER + const ixMt = response.mediaType; + if (ixMt === BANNER || ixMt === "banner" || ixMt === 1) { + bid.ad = response.ad || ''; + return [bid]; + } + + // VIDEO + let context; + let adUnitCode; + + if (ixReq.videoContext) { + context = ixReq.videoContext; + adUnitCode = ixReq.adUnitCode; + } + + if (!context && ixReq.bidderRequestData) { + let ixDecoded = ixReq.bidderRequestData; + + if (typeof ixDecoded === 'string') { + try { + ixDecoded = JSON.parse(decodeURIComponent(ixDecoded)); + } catch (e) { + ixDecoded = null; } } - if (context === INSTREAM) { - responseBid.vastUrl = response.ad || ''; - } else if (context === OUTSTREAM) { - responseBid.vastXml = response.ad || ''; - if (response.rUrl) { - responseBid.renderer = createRenderer({ ...response, adUnitCode }); + + if (ixDecoded?.bids?.length) { + for (const item of ixDecoded.bids) { + if (item.bidId === response.slotBidId) { + context = item.mediaTypes?.video?.context; + adUnitCode = item.adUnitCode; + break; + } } } } - 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() - }); - }); + + // INSTREAM + if (context === INSTREAM) { + bid.vastUrl = response.ad || ''; + } else if (context === OUTSTREAM) { + // OUTSTREAM + bid.vastXml = response.ad || ''; + + if (response.rUrl) { + bid.renderer = createRenderer({ + ...response, + adUnitCode }); - } catch (e) { } - return renderer; } - return bids; + + return [bid]; } }; diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 447947e43a4..b00c1e7bef8 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -5,7 +5,7 @@ import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, d import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'insticator'; -const ENDPOINT = 'https://ex.ingage.tech/v1/openrtb'; // production endpoint +const ENDPOINT = 'https://ex.ingage.tech/v1/openrtb'; const USER_ID_KEY = 'hb_insticator_uid'; const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes @@ -29,7 +29,16 @@ export const OPTIONAL_VIDEO_PARAMS = { 'playbackend': (value) => isInteger(value) && [1, 2, 3].includes(value), 'delivery': (value) => isArrayOfNums(value), 'pos': (value) => isInteger(value) && [0, 1, 2, 3, 4, 5, 6, 7].includes(value), - 'api': (value) => isArrayOfNums(value)}; + 'api': (value) => isArrayOfNums(value), + // ORTB 2.6 video parameters + 'podid': (value) => typeof value === 'string' && value.length > 0, + 'podseq': (value) => isInteger(value) && value >= 0, + 'poddur': (value) => isInteger(value) && value > 0, + 'slotinpod': (value) => isInteger(value) && [-1, 0, 1, 2].includes(value), + 'mincpmpersec': (value) => typeof value === 'number' && value > 0, + 'maxseq': (value) => isInteger(value) && value > 0, + 'rqddurs': (value) => isArrayOfNums(value) && value.every(v => v > 0), +}; const ORTB_SITE_FIRST_PARTY_DATA = { 'cat': v => Array.isArray(v) && v.every(c => typeof c === 'string'), @@ -114,11 +123,11 @@ function buildVideo(bidRequest) { const optionalParams = {}; for (const param in OPTIONAL_VIDEO_PARAMS) { - if (bidRequestVideo[param] && OPTIONAL_VIDEO_PARAMS[param](bidRequestVideo[param])) { + if (bidRequestVideo[param] != null && OPTIONAL_VIDEO_PARAMS[param](bidRequestVideo[param])) { optionalParams[param] = bidRequestVideo[param]; } // remove invalid optional params from bidder specific overrides - if (videoBidderParams[param] && !OPTIONAL_VIDEO_PARAMS[param](videoBidderParams[param])) { + if (videoBidderParams[param] != null && !OPTIONAL_VIDEO_PARAMS[param](videoBidderParams[param])) { delete videoBidderParams[param]; } } @@ -442,8 +451,9 @@ function buildRequest(validBidRequests, bidderRequest) { return req; } -function buildBid(bid, bidderRequest) { +function buildBid(bid, bidderRequest, seatbid) { const originalBid = ((bidderRequest.bids) || []).find((b) => b.bidId === bid.impid); + let meta = {} if (bid.ext && bid.ext.meta) { @@ -454,27 +464,79 @@ function buildBid(bid, bidderRequest) { meta.advertiserDomains = bid.adomain } + // ORTB 2.6: Add category support + if (bid.cat && Array.isArray(bid.cat) && bid.cat.length > 0) { + meta.primaryCatId = bid.cat[0]; + if (bid.cat.length > 1) { + meta.secondaryCatIds = bid.cat.slice(1); + } + } + + // ORTB 2.6: Add seat from seatbid + if (seatbid && seatbid.seat) { + meta.seat = seatbid.seat; + } + + // ORTB 2.6: Add creative attributes + if (bid.attr && Array.isArray(bid.attr)) { + meta.attr = bid.attr; + } + + // Determine media type using multiple signals let mediaType = 'banner'; - if (bid.adm && bid.adm.includes(' 0 ? Math.min(bid.exp, configTTL) : configTTL; + const bidResponse = { requestId: bid.impid, creativeId: bid.crid, cpm: bid.price, currency: 'USD', netRevenue: true, - ttl: bid.exp || config.getConfig('insticator.bidTTL') || BID_TTL, + ttl: ttl, width: bid.w, height: bid.h, mediaType: mediaType, ad: bid.adm, - adUnitCode: originalBid.adUnitCode, + adUnitCode: originalBid?.adUnitCode, ...(Object.keys(meta).length > 0 ? {meta} : {}) }; + // ORTB 2.6: Add deal ID + if (bid.dealid) { + bidResponse.dealId = bid.dealid; + } + + // ORTB 2.6: Add billing URL for billing notification + if (bid.burl) { + bidResponse.burl = bid.burl; + } + + // ORTB 2.6: Add notice URL for win notification + if (bid.nurl) { + bidResponse.nurl = bid.nurl; + } + if (mediaType === 'video') { bidResponse.vastXml = bid.adm; + + // ORTB 2.6: Add video duration for adpod support + if (bid.dur && isInteger(bid.dur) && bid.dur > 0) { + bidResponse.video = bidResponse.video || {}; + bidResponse.video.durationSeconds = bid.dur; + } } // Inticator bid adaptor only returns `vastXml` for video bids. No VastUrl or videoCache. @@ -493,7 +555,7 @@ function buildBid(bid, bidderRequest) { } function buildBidSet(seatbid, bidderRequest) { - return seatbid.bid.map((bid) => buildBid(bid, bidderRequest)); + return seatbid.bid.map((bid) => buildBid(bid, bidderRequest, seatbid)); } function validateSize(size) { @@ -652,9 +714,19 @@ export const spec = { if (deepAccess(validBidRequests[0], 'params.bid_endpoint_request_url')) { endpointUrl = deepAccess(validBidRequests[0], 'params.bid_endpoint_request_url').replace(/^http:/, 'https:'); } + + // Add publisherId as query parameter if present and non-empty + const publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); + if (publisherId && publisherId.trim() !== '') { + const urlObj = new URL(endpointUrl); + urlObj.searchParams.set('publisherId', publisherId); + endpointUrl = urlObj.toString(); + } } if (validBidRequests.length > 0) { + const ortbRequest = buildRequest(validBidRequests, bidderRequest); + requests.push({ method: 'POST', url: endpointUrl, @@ -662,7 +734,7 @@ export const spec = { contentType: 'application/json', withCredentials: true, }, - data: JSON.stringify(buildRequest(validBidRequests, bidderRequest)), + data: JSON.stringify(ortbRequest), bidderRequest, }); } @@ -673,11 +745,22 @@ export const spec = { interpretResponse: function (serverResponse, request) { const bidderRequest = request.bidderRequest; const body = serverResponse.body; - if (!body || body.id !== bidderRequest.bidderRequestId) { - logError('insticator: response id does not match bidderRequestId'); + + // Handle 204 No Content or empty response body (valid "no bid" scenario) + if (!body || !body.id) { return []; } + // Validate response ID matches request ID + if (body.id !== bidderRequest.bidderRequestId) { + logError('insticator: response id does not match bidderRequestId', { + responseId: body.id, + bidderRequestId: bidderRequest.bidderRequestId + }); + return []; + } + + // No seatbid means no bids (valid scenario) if (!body.seatbid) { return []; } @@ -686,7 +769,9 @@ export const spec = { buildBidSet(seatbid, bidderRequest) ); - return bidsets.reduce((a, b) => a.concat(b), []); + const finalBids = bidsets.reduce((a, b) => a.concat(b), []); + + return finalBids; }, getUserSyncs: function (options, responses) { diff --git a/modules/insuradsBidAdapter.md b/modules/insuradsBidAdapter.md new file mode 100644 index 00000000000..11c0bc248e7 --- /dev/null +++ b/modules/insuradsBidAdapter.md @@ -0,0 +1,55 @@ +# Overview + +``` +Module Name: InsurAds Bid Adapter +Module Type: Bidder Adapter +Maintainer: jclimaco@insurads.com +``` + +# Description + +Connects to InsurAds network for bids. + +# Test Parameters + +## Web + +### Display +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'insurads', + params: { + tagId: 'ToBeSupplied' + } + }] + }, +]; +``` + +### Video Instream +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'insurads', + params: { + tagId: 'ToBeSupplied' + } + }] + }; +``` diff --git a/modules/insuradsBidAdapter.ts b/modules/insuradsBidAdapter.ts new file mode 100644 index 00000000000..30f506118b7 --- /dev/null +++ b/modules/insuradsBidAdapter.ts @@ -0,0 +1,149 @@ +import { deepSetValue, generateUUID, logError } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' + +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'insurads'; +const REQUEST_URL = 'https://fast.nexx360.io/booster'; +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '7.1'; +const GVLID = 596; +const ALT_KEY = 'nexx360_storage'; + +const DEFAULT_GZIP_ENABLED = false; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type InsurAdsBidParams = RequireAtLeastOne<{ + tagId?: string; + placement?: string; + videoTagId?: string; + nativeTagId?: string; + adUnitPath?: string; + adUnitName?: string; + divId?: string; + allBids?: boolean; + customId?: string; + bidders?: Record; +}, "tagId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + ['nexx360']: InsurAdsBidParams; + } +} + +export const STORAGE = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +export const getInsurAdsLocalStorage = getLocalStorageFunctionGenerator<{ nexx360Id: string }>( + STORAGE, + BIDDER_CODE, + ALT_KEY, + 'nexx360Id' +); + +export const getGzipSetting = (): boolean => { + const bidderConfig = config.getBidderConfig(); + const gzipEnabled = bidderConfig.insurads?.gzipEnabled; + + if (gzipEnabled === true || gzipEnabled === 'true') { + return true; + } + return DEFAULT_GZIP_ENABLED; +} + +const converter = ortbConverter({ + context: { + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest: BidRequest, context) { + let imp: ORTBImp = buildImp(bidRequest, context); + imp = enrichImp(imp, bidRequest); + const divId = bidRequest.params.divId || bidRequest.adUnitCode; + const slotEl: HTMLElement | null = typeof divId === 'string' ? document.getElementById(divId) : null; + if (slotEl) { + const { width, height } = getBoundingClientRect(slotEl); + deepSetValue(imp, 'ext.dimensions.slotW', width); + deepSetValue(imp, 'ext.dimensions.slotH', height); + deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); + deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); + } + deepSetValue(imp, 'ext.nexx360', bidRequest.params); + deepSetValue(imp, 'ext.nexx360.divId', divId); + if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); + if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + let request: ORTBRequest = buildRequest(imps, bidderRequest, context); + const amxId = getAmxId(STORAGE, BIDDER_CODE); + request = enrichRequest(request, amxId, PAGE_VIEW_ID, BIDDER_VERSION); + return request; + }, +}); + +const isBidRequestValid = (bid: BidRequest): boolean => { + if (bid.params.adUnitName && (typeof bid.params.adUnitName !== 'string' || bid.params.adUnitName === '')) { + logError('bid.params.adUnitName needs to be a string'); + return false; + } + if (bid.params.adUnitPath && (typeof bid.params.adUnitPath !== 'string' || bid.params.adUnitPath === '')) { + logError('bid.params.adUnitPath needs to be a string'); + return false; + } + if (bid.params.divId && (typeof bid.params.divId !== 'string' || bid.params.divId === '')) { + logError('bid.params.divId needs to be a string'); + return false; + } + if (bid.params.allBids && typeof bid.params.allBids !== 'boolean') { + logError('bid.params.allBids needs to be a boolean'); + return false; + } + if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId && !bid.params.placement) { + logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId or bid.params.placement must be defined'); + return false; + } + return true; +}; + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data: ORTBRequest = converter.toORTB({ bidRequests, bidderRequest }) + const adapterRequest: AdapterRequest = { + method: 'POST', + url: REQUEST_URL, + data, + options: { + endpointCompression: getGzipSetting() + }, + } + return adapterRequest; +} + +export const spec: BidderSpec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 06c9bcb28b4..bf179d3e880 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -2,38 +2,34 @@ import { isPlainObject, logError, logInfo } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { config } from '../src/config.js'; import { EVENTS } from '../src/constants.js'; -import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; import { detectBrowser } from '../libraries/intentIqUtils/detectBrowserUtils.js'; import { appendSPData } from '../libraries/intentIqUtils/urlUtils.js'; -import { appendVrrefAndFui, getReferrer } from '../libraries/intentIqUtils/getRefferer.js'; +import { appendVrrefAndFui, getCurrentUrl, getRelevantRefferer } from '../libraries/intentIqUtils/getRefferer.js'; import { getCmpData } from '../libraries/intentIqUtils/getCmpData.js'; +import { getUnitPosition } from '../libraries/intentIqUtils/getUnitPosition.js'; import { - CLIENT_HINTS_KEY, - FIRST_PARTY_KEY, VERSION, - PREBID + PREBID, + WITH_IIQ } from '../libraries/intentIqConstants/intentIqConstants.js'; -import { readData, defineStorageType } from '../libraries/intentIqUtils/storageUtils.js'; import { reportingServerAddress } from '../libraries/intentIqUtils/intentIqConfig.js'; import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.js'; import { gamPredictionReport } from '../libraries/intentIqUtils/gamPredictionReport.js'; +import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const MODULE_NAME = 'iiqAnalytics'; const analyticsType = 'endpoint'; -const storage = getStorageManager({ - moduleType: MODULE_TYPE_ANALYTICS, - moduleName: MODULE_NAME -}); const prebidVersion = '$prebid.version$'; +const pbjs = getGlobal(); export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); -const allowedStorage = defineStorageType(config.enabledStorageTypes); let globalName; +let identityGlobalName; let alreadySubscribedOnGAM = false; let reportList = {}; let cleanReportsID; +let iiqConfig; const PARAMS_NAMES = { abTestGroup: 'abGroup', @@ -70,50 +66,48 @@ const PARAMS_NAMES = { partnerId: 'partnerId', firstPartyId: 'pcid', placementId: 'placementId', - adType: 'adType' + adType: 'adType', + abTestUuid: 'abTestUuid', }; -function getIntentIqConfig() { - return config.getConfig('userSync.userIds')?.find((m) => m.name === 'intentIqId'); -} - const DEFAULT_URL = 'https://reports.intentiq.com/report'; const getDataForDefineURL = () => { - const cmpData = getCmpData(); - const gdprDetected = cmpData.gdprString; - - return [iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress, gdprDetected]; + return [iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress, iiqAnalyticsAnalyticsAdapter.initOptions.region]; }; -const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, analyticsType }), { - initOptions: { - lsValueInitialized: false, +const getDefaultInitOptions = () => { + return { + adapterConfigInitialized: false, partner: null, fpid: null, currentGroup: null, dataInLs: null, eidl: null, - lsIdsInitialized: false, + dataIdsInitialized: false, manualWinReportEnabled: false, domainName: null, siloEnabled: false, reportMethod: null, + abPercentage: null, + abTestUuid: null, additionalParams: null, - reportingServerAddress: '' - }, + reportingServerAddress: '', + region: '' + } +} + +const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, analyticsType }), { + initOptions: getDefaultInitOptions(), track({ eventType, args }) { switch (eventType) { case BID_WON: bidWon(args); break; case BID_REQUESTED: - checkAndInitConfig(); - defineGlobalVariableName(); if (!alreadySubscribedOnGAM && shouldSubscribeOnGAM()) { alreadySubscribedOnGAM = true; - const iiqConfig = getIntentIqConfig(); - gamPredictionReport(iiqConfig?.params?.gamObjectReference, bidWon); + gamPredictionReport(iiqConfig?.gamObjectReference, bidWon); } break; default: @@ -126,90 +120,72 @@ const iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ url: DEFAULT_URL, a const { BID_WON, BID_REQUESTED } = EVENTS; function initAdapterConfig(config) { - if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; - const iiqIdSystemConfig = getIntentIqConfig(); - - if (iiqIdSystemConfig) { - const { manualWinReportEnabled, gamPredictReporting, reportMethod, reportingServerAddress: reportEndpoint, adUnitConfig } = config?.options || {} - iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; - iiqAnalyticsAnalyticsAdapter.initOptions.partner = - iiqIdSystemConfig.params?.partner && !isNaN(iiqIdSystemConfig.params.partner) ? iiqIdSystemConfig.params.partner : -1; - - iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = - typeof iiqIdSystemConfig.params?.browserBlackList === 'string' - ? iiqIdSystemConfig.params.browserBlackList.toLowerCase() - : ''; - iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = + if (iiqAnalyticsAnalyticsAdapter.initOptions.adapterConfigInitialized) return; + + const options = config?.options || {} + iiqConfig = options + const { manualWinReportEnabled, gamPredictReporting, reportMethod, reportingServerAddress, region, adUnitConfig, partner, ABTestingConfigurationSource, browserBlackList, domainName, additionalParams } = options + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = manualWinReportEnabled || false; - iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqIdSystemConfig.params?.domainName || ''; - iiqAnalyticsAnalyticsAdapter.initOptions.siloEnabled = - typeof iiqIdSystemConfig.params?.siloEnabled === 'boolean' ? iiqIdSystemConfig.params.siloEnabled : false; - iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = parseReportingMethod(reportMethod); - iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams = iiqIdSystemConfig.params?.additionalParams || null; - iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting = typeof gamPredictReporting === 'boolean' ? gamPredictReporting : false; - iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress = typeof reportEndpoint === 'string' ? reportEndpoint : ''; - iiqAnalyticsAnalyticsAdapter.initOptions.adUnitConfig = typeof adUnitConfig === 'number' ? adUnitConfig : 1; - } else { - logError('IIQ ANALYTICS -> there is no initialized intentIqIdSystem module') - iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = false; - iiqAnalyticsAnalyticsAdapter.initOptions.partner = -1; - iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = 'GET'; - } + iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod = parseReportingMethod(reportMethod); + iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting = typeof gamPredictReporting === 'boolean' ? gamPredictReporting : false; + iiqAnalyticsAnalyticsAdapter.initOptions.reportingServerAddress = typeof reportingServerAddress === 'string' ? reportingServerAddress : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.region = typeof region === 'string' ? region : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.adUnitConfig = typeof adUnitConfig === 'number' ? adUnitConfig : 1; + iiqAnalyticsAnalyticsAdapter.initOptions.configSource = ABTestingConfigurationSource; + iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = defineABTestingGroup(options); + iiqAnalyticsAnalyticsAdapter.initOptions.idModuleConfigInitialized = true; + iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = + typeof browserBlackList === 'string' + ? browserBlackList.toLowerCase() + : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.domainName = domainName || ''; + iiqAnalyticsAnalyticsAdapter.initOptions.additionalParams = additionalParams || null; + if (!partner) { + logError('IIQ ANALYTICS -> partner ID is missing'); + iiqAnalyticsAnalyticsAdapter.initOptions.partner = -1 + } else iiqAnalyticsAnalyticsAdapter.initOptions.partner = partner + defineGlobalVariableName(); + iiqAnalyticsAnalyticsAdapter.initOptions.adapterConfigInitialized = true } -function initReadLsIds() { +function receivePartnerData() { try { iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; - iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse( - readData( - `${FIRST_PARTY_KEY}${ - iiqAnalyticsAnalyticsAdapter.initOptions.siloEnabled - ? '_p_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner - : '' - }`, - allowedStorage, - storage - ) - ); - if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { - iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; + const FPD = window[identityGlobalName]?.firstPartyData + if (!window[identityGlobalName] || !FPD) { + return false } - const partnerData = readData( - FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, - allowedStorage, - storage - ); - const clientsHints = readData(CLIENT_HINTS_KEY, allowedStorage, storage) || ''; + iiqAnalyticsAnalyticsAdapter.initOptions.fpid = FPD + const { partnerData, clientHints = '', actualABGroup } = window[identityGlobalName] if (partnerData) { - iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; - const pData = JSON.parse(partnerData); - iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause; - iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; - iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; - iiqAnalyticsAnalyticsAdapter.initOptions.clientType = pData.clientType || null; - iiqAnalyticsAnalyticsAdapter.initOptions.siteId = pData.siteId || null; - iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll = pData.wsrvcll || false; - iiqAnalyticsAnalyticsAdapter.initOptions.rrtt = pData.rrtt || null; + iiqAnalyticsAnalyticsAdapter.initOptions.dataIdsInitialized = true; + iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = partnerData.terminationCause; + iiqAnalyticsAnalyticsAdapter.initOptions.abTestUuid = partnerData.abTestUuid; + iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = partnerData.data; + iiqAnalyticsAnalyticsAdapter.initOptions.eidl = partnerData.eidl || -1; + iiqAnalyticsAnalyticsAdapter.initOptions.clientType = partnerData.clientType || null; + iiqAnalyticsAnalyticsAdapter.initOptions.siteId = partnerData.siteId || null; + iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll = partnerData.wsrvcll || false; + iiqAnalyticsAnalyticsAdapter.initOptions.rrtt = partnerData.rrtt || null; } - iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints; + if (actualABGroup) { + iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = actualABGroup; + } + iiqAnalyticsAnalyticsAdapter.initOptions.clientHints = clientHints; } catch (e) { logError(e); + return false; } } function shouldSubscribeOnGAM() { - const iiqConfig = getIntentIqConfig(); - if (!iiqConfig?.params?.gamObjectReference || !isPlainObject(iiqConfig.params.gamObjectReference)) return false; - const partnerDataFromLS = readData( - FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, - allowedStorage, - storage - ); + if (!iiqConfig?.gamObjectReference || !isPlainObject(iiqConfig.gamObjectReference)) return false; + const partnerData = window[identityGlobalName]?.partnerData - if (partnerDataFromLS) { - const partnerData = JSON.parse(partnerDataFromLS); + if (partnerData) { return partnerData.gpr || (!('gpr' in partnerData) && iiqAnalyticsAnalyticsAdapter.initOptions.gamPredictReporting); } return false; @@ -228,20 +204,11 @@ export function restoreReportList() { reportList = {}; } -function checkAndInitConfig() { - if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { - initAdapterConfig(); - } -} - function bidWon(args, isReportExternal) { - checkAndInitConfig(); - if ( - isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || - iiqAnalyticsAnalyticsAdapter.initOptions.partner === -1 + isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) ) { - return; + iiqAnalyticsAnalyticsAdapter.initOptions.partner = -1; } const currentBrowserLowerCase = detectBrowser(); if (iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList?.includes(currentBrowserLowerCase)) { @@ -249,15 +216,13 @@ function bidWon(args, isReportExternal) { return; } - if ( - iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && - !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized - ) { - initReadLsIds(); - } if (shouldSendReport(isReportExternal)) { - const preparedPayload = preparePayload(args, true); + const success = receivePartnerData(); + const preparedPayload = preparePayload(args); if (!preparedPayload) return false; + if (success === false) { + preparedPayload[PARAMS_NAMES.terminationCause] = -1 + } const { url, method, payload } = constructFullUrl(preparedPayload); if (method === 'POST') { ajax(url, undefined, payload, { @@ -292,9 +257,9 @@ function defineGlobalVariableName() { return bidWon(args, true); } - const iiqConfig = getIntentIqConfig(); - const partnerId = iiqConfig?.params?.partner || 0; + const partnerId = iiqConfig?.partner || 0; globalName = `intentIqAnalyticsAdapter_${partnerId}`; + identityGlobalName = `iiq_identity_${partnerId}` window[globalName] = { reportExternalWin }; } @@ -305,39 +270,48 @@ function getRandom(start, end) { export function preparePayload(data) { const result = getDefaultDataObject(); - + const fullUrl = getCurrentUrl(); result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; result[PARAMS_NAMES.prebidVersion] = prebidVersion; - result[PARAMS_NAMES.referrer] = getReferrer(); + result[PARAMS_NAMES.referrer] = getRelevantRefferer(iiqAnalyticsAnalyticsAdapter.initOptions.domainName, fullUrl); result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause; - result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; result[PARAMS_NAMES.clientType] = iiqAnalyticsAnalyticsAdapter.initOptions.clientType; result[PARAMS_NAMES.siteId] = iiqAnalyticsAnalyticsAdapter.initOptions.siteId; result[PARAMS_NAMES.wasServerCalled] = iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll; result[PARAMS_NAMES.requestRtt] = iiqAnalyticsAnalyticsAdapter.initOptions.rrtt; + result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup === WITH_IIQ; - result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup === 'A'; - + if (iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup) { + result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; + } result[PARAMS_NAMES.agentId] = REPORTER_ID; + if (iiqAnalyticsAnalyticsAdapter.initOptions.abTestUuid) { + result[PARAMS_NAMES.abTestUuid] = iiqAnalyticsAnalyticsAdapter.initOptions.abTestUuid; + } if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) { result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid); } if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pid) { result[PARAMS_NAMES.profile] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pid); } + if (iiqAnalyticsAnalyticsAdapter.initOptions.configSource) { + result[PARAMS_NAMES.ABTestingConfigurationSource] = iiqAnalyticsAnalyticsAdapter.initOptions.configSource + } prepareData(data, result); - if (!reportList[result.placementId] || !reportList[result.placementId][result.prebidAuctionId]) { - reportList[result.placementId] = reportList[result.placementId] - ? { ...reportList[result.placementId], [result.prebidAuctionId]: 1 } - : { [result.prebidAuctionId]: 1 }; - cleanReportsID = setTimeout(() => { - if (cleanReportsID) clearTimeout(cleanReportsID); - restoreReportList(); - }, 1500); // clear object in 1.5 second after defining reporting list - } else { - logError('Duplication detected, report will be not sent'); - return; + if (shouldSubscribeOnGAM()) { + if (!reportList[result.placementId] || !reportList[result.placementId][result.prebidAuctionId]) { + reportList[result.placementId] = reportList[result.placementId] + ? { ...reportList[result.placementId], [result.prebidAuctionId]: 1 } + : { [result.prebidAuctionId]: 1 }; + cleanReportsID = setTimeout(() => { + if (cleanReportsID) clearTimeout(cleanReportsID); + restoreReportList(); + }, 1500); // clear object in 1.5 second after defining reporting list + } else { + logError('Duplication detected, report will be not sent'); + return; + } } fillEidsData(result); @@ -346,7 +320,7 @@ export function preparePayload(data) { } function fillEidsData(result) { - if (iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + if (iiqAnalyticsAnalyticsAdapter.initOptions.dataIdsInitialized) { result[PARAMS_NAMES.hadEidsInLocalStorage] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl && iiqAnalyticsAnalyticsAdapter.initOptions.eidl > 0; result[PARAMS_NAMES.auctionEidsLength] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl || -1; @@ -374,6 +348,15 @@ function prepareData(data, result) { if (data.status) { result.status = data.status; } + if (data.size) { + result.size = data.size; + } + if (typeof data.pos === 'number') { + result.pos = data.pos; + } else if (data.adUnitCode) { + const pos = getUnitPosition(pbjs, data.adUnitCode); + if (typeof pos === 'number') result.pos = pos; + } result.prebidAuctionId = data.auctionId || data.prebidAuctionId; @@ -427,7 +410,6 @@ function getDefaultDataObject() { pbjsver: prebidVersion, partnerAuctionId: 'BW', reportSource: 'pbjs', - abGroup: 'U', jsversion: VERSION, partnerId: -1, biddingPlatformId: 1, @@ -440,6 +422,7 @@ function getDefaultDataObject() { function constructFullUrl(data) { const report = []; const reportMethod = iiqAnalyticsAnalyticsAdapter.initOptions.reportMethod; + const partnerData = window[identityGlobalName]?.partnerData; const currentBrowserLowerCase = detectBrowser(); data = btoa(JSON.stringify(data)); report.push(data); @@ -462,11 +445,11 @@ function constructFullUrl(data) { '&source=' + PREBID + '&uh=' + - encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientHints) + (cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '') + (cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '') + (cmpData.gdprString ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1' : '&gdpr=0'); - url = appendSPData(url, iiqAnalyticsAnalyticsAdapter.initOptions.fpid); + url = appendSPData(url, partnerData); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); if (reportMethod === 'POST') { @@ -488,6 +471,18 @@ iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function initAdapterConfig(myConfig) }; + +iiqAnalyticsAnalyticsAdapter.originDisableAnalytics = iiqAnalyticsAnalyticsAdapter.disableAnalytics; +iiqAnalyticsAnalyticsAdapter.disableAnalytics = function() { + globalName = undefined; + identityGlobalName = undefined; + alreadySubscribedOnGAM = false; + reportList = {}; + cleanReportsID = undefined; + iiqConfig = undefined; + iiqAnalyticsAnalyticsAdapter.initOptions = getDefaultInitOptions() + iiqAnalyticsAnalyticsAdapter.originDisableAnalytics() +}; adapterManager.registerAnalyticsAdapter({ adapter: iiqAnalyticsAnalyticsAdapter, code: MODULE_NAME diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md index 9389cb7d8ee..c691496df59 100644 --- a/modules/intentIqAnalyticsAdapter.md +++ b/modules/intentIqAnalyticsAdapter.md @@ -21,11 +21,22 @@ No registration for this module is required. {: .table .table-bordered .table-striped } | Parameter | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | +| options.partner| Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | | options.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `false` | | options.reportMethod | Optional | String | Defines the HTTP method used to send the analytics report. If set to `"POST"`, the report payload will be sent in the body of the request. If set to `"GET"` (default), the payload will be included as a query parameter in the request URL. | `"GET"` | | options.reportingServerAddress | Optional | String | The base URL for the IntentIQ reporting server. If parameter is provided in `configParams`, it will be used. | `"https://domain.com"` | | options.adUnitConfig | Optional | Number | Determines how the `placementId` parameter is extracted in the report (default is 1). Possible values: 1 – adUnitCode first, 2 – placementId first, 3 – only adUnitCode, 4 – only placementId. | `1` | | options.gamPredictReporting | Optional | Boolean | This variable controls whether the GAM prediction logic is enabled or disabled. The main purpose of this logic is to extract information from a rendered GAM slot when no Prebid bidWon event is available. In that case, we take the highest CPM from the current auction and add 0.01 to that value. | `false` | +| options.ABTestingConfigurationSource | Optional | String | Determines how AB group will be defined. Possible values: `"IIQServer"` – group defined by IIQ server, `"percentage"` – generated group based on abPercentage, `"group"` – define group based on value provided by partner. | `IIQServer` | +| options.abPercentage | Optional | Number | Percentage for A/B testing group. Default value is `95` | `95` | +| options.group | Optional | String | Define group provided by partner, possible values: `"A"`, `"B"` | `"A"` | +| options.gamObjectReference | Optional | Object | This is a reference to the Google Ad Manager (GAM) object, which will be used to set targeting. If this parameter is not provided, the group reporting will not be configured.| `googletag`| +| options.browserBlackList | Optional | String | This is the name of a browser that can be added to a blacklist.| `"chrome"`| +| options.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages.| `"currentDomain.com"`| +| options. additionalParams | Optional | Array | This parameter allows sending additional custom key-value parameters with specific destination logic (sync, VR, winreport). Each custom parameter is defined as an object in the array. | `[ { parameterName: “abc”, parameterValue: 123, destination: [1,1,0] } ]` | +| options. additionalParams[0].parameterName | Required | String | Name of the custom parameter. This will be sent as a query parameter. | `"abc"` | +| options. additionalParams[0].parameterValue | Required | String / Number | Value to assign to the parameter. | `123` | +| options. additionalParams[0].destination | Required | Array | Array of numbers either `1` or `0`. Controls where this parameter is sent `[sendWithSync, sendWithVr, winreport]`. | `[1, 0, 0]` | #### Example Configuration @@ -33,10 +44,9 @@ No registration for this module is required. pbjs.enableAnalytics({ provider: 'iiqAnalytics', options: { - manualWinReportEnabled: false, - reportMethod: "GET", - adUnitConfig: 1, - gamPredictReporting: false + partner: 1177538, + ABTestingConfigurationSource: 'IIQServer', + domainName: "currentDomain.com", } }); ``` @@ -77,7 +87,9 @@ originalCpm: 1.5, // Original CPM value. originalCurrency: 'USD', // Original currency. status: 'rendered', // Auction status, e.g., 'rendered'. placementId: 'div-1' // ID of the ad placement. -adType: 'banner' // Specifies the type of ad served +adType: 'banner', // Specifies the type of ad served, +size: '320x250', // Size of adUnit item, +pos: 0 // The following values are defined in the ORTB 2.5 spec } ``` @@ -95,6 +107,8 @@ adType: 'banner' // Specifies the type of ad served | status | String | Status of the impression. Leave empty or undefined if Prebid is not the bidding platform | rendered | No | | placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No | | adType | String | Specifies the type of ad served. Possible values: “banner“, “video“, “native“, “audio“. | banner | No | +| size | String | Size of adUnit item | '320x250' | No | +| pos | number | The pos field specifies the position of the adUnit on the page according to the OpenRTB 2.5 specification | 0 | No | To report the auction win, call the function as follows: diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 53755afa050..054afe82371 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,29 +5,28 @@ * @requires module:modules/userId */ -import {logError, isPlainObject, isStr, isNumber} from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js' -import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; -import {appendSPData} from '../libraries/intentIqUtils/urlUtils.js'; +import { logError, isPlainObject, isStr, isNumber } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js' +import { detectBrowser } from '../libraries/intentIqUtils/detectBrowserUtils.js'; +import { appendSPData } from '../libraries/intentIqUtils/urlUtils.js'; import { isCHSupported } from '../libraries/intentIqUtils/chUtils.js' -import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js'; +import { appendVrrefAndFui } from '../libraries/intentIqUtils/getRefferer.js'; import { getCmpData } from '../libraries/intentIqUtils/getCmpData.js'; -import {readData, storeData, defineStorageType, removeDataByKey, tryParse} from '../libraries/intentIqUtils/storageUtils.js'; +import { readData, storeData, defineStorageType, removeDataByKey, tryParse } from '../libraries/intentIqUtils/storageUtils.js'; import { FIRST_PARTY_KEY, - WITH_IIQ, WITHOUT_IIQ, - NOT_YET_DEFINED, CLIENT_HINTS_KEY, EMPTY, GVLID, VERSION, INVALID_ID, SYNC_REFRESH_MILL, META_DATA_CONSTANT, PREBID, - HOURS_24, CH_KEYS + HOURS_72, CH_KEYS } from '../libraries/intentIqConstants/intentIqConstants.js'; -import {SYNC_KEY} from '../libraries/intentIqUtils/getSyncKey.js'; -import {iiqPixelServerAddress, iiqServerAddress} from '../libraries/intentIqUtils/intentIqConfig.js'; +import { SYNC_KEY } from '../libraries/intentIqUtils/getSyncKey.js'; +import { iiqPixelServerAddress, getIiqServerAddress } from '../libraries/intentIqUtils/intentIqConfig.js'; import { handleAdditionalParams } from '../libraries/intentIqUtils/handleAdditionalParams.js'; import { decryptData, encryptData } from '../libraries/intentIqUtils/cryptionUtils.js'; +import { defineABTestingGroup } from '../libraries/intentIqUtils/defineABTestingGroupUtils.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -50,6 +49,7 @@ const encoderCH = { }; let sourceMetaData; let sourceMetaDataExternal; +let globalName = '' let FIRST_PARTY_KEY_FINAL = FIRST_PARTY_KEY; let PARTNER_DATA_KEY; @@ -58,6 +58,9 @@ let failCount = 0; let noDataCount = 0; export let firstPartyData; +let partnerData; +let clientHints; +let actualABGroup /** * Generate standard UUID string @@ -78,7 +81,7 @@ function addUniquenessToUrl(url) { return url; } -function appendFirstPartyData (url, firstPartyData, partnerData) { +function appendFirstPartyData(url, firstPartyData, partnerData) { url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : ''; url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : ''; @@ -90,7 +93,7 @@ function verifyIdType(value) { return -1; } -function appendPartnersFirstParty (url, configParams) { +function appendPartnersFirstParty(url, configParams) { const partnerClientId = typeof configParams.partnerClientId === 'string' ? encodeURIComponent(configParams.partnerClientId) : ''; const partnerClientIdType = typeof configParams.partnerClientIdType === 'number' ? verifyIdType(configParams.partnerClientIdType) : -1; @@ -102,7 +105,7 @@ function appendPartnersFirstParty (url, configParams) { return url; } -function appendCMPData (url, cmpData) { +function appendCMPData(url, cmpData) { url += cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : ''; url += cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : ''; url += cmpData.gdprApplies @@ -111,7 +114,7 @@ function appendCMPData (url, cmpData) { return url } -function appendCounters (url) { +function appendCounters(url) { url += '&jaesc=' + encodeURIComponent(callCount); url += '&jafc=' + encodeURIComponent(failCount); url += '&jaensc=' + encodeURIComponent(noDataCount); @@ -143,6 +146,15 @@ function addMetaData(url, data) { return url + '&fbp=' + data; } +export function initializeGlobalIIQ(partnerId) { + if (!globalName || !window[globalName]) { + globalName = `iiq_identity_${partnerId}` + window[globalName] = {} + return true + } + return false +} + export function createPixelUrl(firstPartyData, clientHints, configParams, partnerData, cmpData) { const browser = detectBrowser(); @@ -159,7 +171,7 @@ export function createPixelUrl(firstPartyData, clientHints, configParams, partne url = appendCMPData(url, cmpData); url = addMetaData(url, sourceMetaDataExternal || sourceMetaData); url = handleAdditionalParams(browser, url, 0, configParams.additionalParams); - url = appendSPData(url, firstPartyData) + url = appendSPData(url, partnerData); url += '&source=' + PREBID; return url; } @@ -172,7 +184,7 @@ function sendSyncRequest(allowedStorage, url, partner, firstPartyData, newUser) const needToDoSync = (Date.now() - (firstPartyData?.date || firstPartyData?.sCal || Date.now())) > SYNC_REFRESH_MILL if (newUser || needToDoSync) { ajax(url, () => { - }, undefined, {method: 'GET', withCredentials: true}); + }, undefined, { method: 'GET', withCredentials: true }); if (firstPartyData?.date) { firstPartyData.date = Date.now() storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); @@ -181,7 +193,7 @@ function sendSyncRequest(allowedStorage, url, partner, firstPartyData, newUser) } else if (!lastSyncDate || lastSyncElapsedTime > SYNC_REFRESH_MILL) { storeData(SYNC_KEY(partner), Date.now() + '', allowedStorage); ajax(url, () => { - }, undefined, {method: 'GET', withCredentials: true}); + }, undefined, { method: 'GET', withCredentials: true }); } } @@ -198,7 +210,7 @@ export function setGamReporting(gamObjectReference, gamParameterName, userGroup, gamObjectReference.cmd.push(() => { gamObjectReference .pubads() - .setTargeting(gamParameterName, userGroup || NOT_YET_DEFINED); + .setTargeting(gamParameterName, userGroup); }); } } @@ -273,7 +285,7 @@ export const intentIqIdSubmodule = { * @returns {{intentIqId: {string}}|undefined} */ decode(value) { - return value && INVALID_ID !== value ? {'intentIqId': value} : undefined; + return value && INVALID_ID !== value ? { 'intentIqId': value } : undefined; }, /** @@ -289,9 +301,11 @@ export const intentIqIdSubmodule = { if (configParams.callback && !callbackFired) { callbackFired = true; if (callbackTimeoutID) clearTimeout(callbackTimeoutID); - if (isGroupB) runtimeEids = { eids: [] }; - configParams.callback(runtimeEids); + let data = runtimeEids; + if (data?.eids?.length === 1 && typeof data.eids[0] === 'string') data = data.eids[0]; + configParams.callback(data); } + updateGlobalObj() } if (typeof configParams.partner !== 'number') { @@ -300,6 +314,8 @@ export const intentIqIdSubmodule = { return; } + initializeGlobalIIQ(configParams.partner) + let decryptedData, callbackTimeoutID; let callbackFired = false; let runtimeEids = { eids: [] }; @@ -315,23 +331,23 @@ export const intentIqIdSubmodule = { PARTNER_DATA_KEY = `${FIRST_PARTY_KEY}_${configParams.partner}`; const allowedStorage = defineStorageType(config.enabledStorageTypes); + partnerData = tryParse(readData(PARTNER_DATA_KEY, allowedStorage)) || {}; let rrttStrtTime = 0; - let partnerData = {}; let shouldCallServer = false; FIRST_PARTY_KEY_FINAL = `${FIRST_PARTY_KEY}${siloEnabled ? '_p_' + configParams.partner : ''}`; const cmpData = getCmpData(); const gdprDetected = cmpData.gdprString; firstPartyData = tryParse(readData(FIRST_PARTY_KEY_FINAL, allowedStorage)); - const isGroupB = firstPartyData?.group === WITHOUT_IIQ; + actualABGroup = defineABTestingGroup(configParams, partnerData?.terminationCause); const currentBrowserLowerCase = detectBrowser(); const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; const isBlacklisted = browserBlackList?.includes(currentBrowserLowerCase); let newUser = false; - setGamReporting(gamObjectReference, gamParameterName, firstPartyData?.group, isBlacklisted); + setGamReporting(gamObjectReference, gamParameterName, actualABGroup, isBlacklisted); - if (groupChanged) groupChanged(firstPartyData?.group || NOT_YET_DEFINED); + if (groupChanged) groupChanged(actualABGroup, partnerData?.terminationCause); callbackTimeoutID = setTimeout(() => { firePartnerCallback(); @@ -343,7 +359,6 @@ export const intentIqIdSubmodule = { firstPartyData = { pcid: firstPartyId, pcidDate: Date.now(), - group: NOT_YET_DEFINED, uspString: EMPTY, gppString: EMPTY, gdprString: EMPTY, @@ -361,7 +376,7 @@ export const intentIqIdSubmodule = { } // Read client hints from storage - let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); + clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); const chSupported = isCHSupported(); let chPromise = null; @@ -401,18 +416,12 @@ export const intentIqIdSubmodule = { return Promise.race([chPromise, timeout]); } - const savedData = tryParse(readData(PARTNER_DATA_KEY, allowedStorage)) - if (savedData) { - partnerData = savedData; - - if (typeof partnerData.callCount === 'number') callCount = partnerData.callCount; - if (typeof partnerData.failCount === 'number') failCount = partnerData.failCount; - if (typeof partnerData.noDataCounter === 'number') noDataCount = partnerData.noDataCounter; - - if (partnerData.wsrvcll) { - partnerData.wsrvcll = false; - storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); - } + if (typeof partnerData.callCount === 'number') callCount = partnerData.callCount; + if (typeof partnerData.failCount === 'number') failCount = partnerData.failCount; + if (typeof partnerData.noDataCounter === 'number') noDataCount = partnerData.noDataCounter; + if (partnerData.wsrvcll) { + partnerData.wsrvcll = false; + storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); } if (partnerData.data) { @@ -422,9 +431,19 @@ export const intentIqIdSubmodule = { } } + function updateGlobalObj() { + if (globalName) { + window[globalName].partnerData = partnerData + window[globalName].firstPartyData = firstPartyData + window[globalName].clientHints = clientHints + window[globalName].actualABGroup = actualABGroup + } + } + + let hasPartnerData = !!Object.keys(partnerData).length; if (!isCMPStringTheSame(firstPartyData, cmpData) || - !firstPartyData.sCal || - (savedData && (!partnerData.cttl || !partnerData.date || Date.now() - partnerData.date > partnerData.cttl))) { + !firstPartyData.sCal || + (hasPartnerData && (!partnerData.cttl || !partnerData.date || Date.now() - partnerData.date > partnerData.cttl))) { firstPartyData.uspString = cmpData.uspString; firstPartyData.gppString = cmpData.gppString; firstPartyData.gdprString = cmpData.gdprString; @@ -433,9 +452,9 @@ export const intentIqIdSubmodule = { storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); } if (!shouldCallServer) { - if (!savedData && !firstPartyData.isOptedOut) { + if (!hasPartnerData && !firstPartyData.isOptedOut) { shouldCallServer = true; - } else shouldCallServer = Date.now() > firstPartyData.sCal + HOURS_24; + } else shouldCallServer = Date.now() > firstPartyData.sCal + HOURS_72; } if (firstPartyData.isOptedOut) { @@ -443,7 +462,7 @@ export const intentIqIdSubmodule = { firePartnerCallback() } - if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids?.eids?.length)) { + if (runtimeEids?.eids?.length) { firePartnerCallback() } @@ -471,14 +490,15 @@ export const intentIqIdSubmodule = { } if (!shouldCallServer) { - if (isGroupB) runtimeEids = { eids: [] }; firePartnerCallback(); updateCountersAndStore(runtimeEids, allowedStorage, partnerData); return { id: runtimeEids.eids }; } + updateGlobalObj() // update global object before server request, to make sure analytical adapter will have it even if the server is "not in time" + // use protocol relative urls for http or https - let url = `${iiqServerAddress(configParams, gdprDetected)}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + let url = `${getIiqServerAddress(configParams)}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; url = appendFirstPartyData(url, firstPartyData, partnerData); url = appendPartnersFirstParty(url, configParams); @@ -488,11 +508,13 @@ export const intentIqIdSubmodule = { url += '&japs=' + encodeURIComponent(configParams.siloEnabled === true); url = appendCounters(url); url += VERSION ? '&jsver=' + VERSION : ''; - url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : ''; + url += actualABGroup ? '&testGroup=' + encodeURIComponent(actualABGroup) : ''; url = addMetaData(url, sourceMetaDataExternal || sourceMetaData); url = handleAdditionalParams(currentBrowserLowerCase, url, 1, additionalParams); - url = appendSPData(url, firstPartyData) + url = appendSPData(url, partnerData) url += '&source=' + PREBID; + url += '&ABTestingConfigurationSource=' + configParams.ABTestingConfigurationSource + url += '&abtg=' + encodeURIComponent(actualABGroup) // Add vrref and fui to the URL url = appendVrrefAndFui(url, configParams.domainName); @@ -506,6 +528,9 @@ export const intentIqIdSubmodule = { const resp = function (callback) { const callbacks = { success: response => { + if (rrttStrtTime && rrttStrtTime > 0) { + partnerData.rrtt = Date.now() - rrttStrtTime; + } const respJson = tryParse(response); // If response is a valid json and should save is true if (respJson) { @@ -520,22 +545,14 @@ export const intentIqIdSubmodule = { if (callbackTimeoutID) clearTimeout(callbackTimeoutID) if ('cttl' in respJson) { partnerData.cttl = respJson.cttl; - } else partnerData.cttl = HOURS_24; + } else partnerData.cttl = HOURS_72; if ('tc' in respJson) { partnerData.terminationCause = respJson.tc; - if (Number(respJson.tc) === 41) { - firstPartyData.group = WITHOUT_IIQ; - storeData(FIRST_PARTY_KEY_FINAL, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); - if (groupChanged) groupChanged(firstPartyData.group); - defineEmptyDataAndFireCallback(); - if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group); - return - } else { - firstPartyData.group = WITH_IIQ; - if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group); - if (groupChanged) groupChanged(firstPartyData.group); - } + actualABGroup = defineABTestingGroup(configParams, respJson.tc,); + + if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, actualABGroup); + if (groupChanged) groupChanged(actualABGroup, partnerData?.terminationCause); } if ('isOptedOut' in respJson) { if (respJson.isOptedOut !== firstPartyData.isOptedOut) { @@ -574,7 +591,7 @@ export const intentIqIdSubmodule = { } 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]} + respJson.data = { eids: [respJson.data] } } } partnerData.data = respJson.data; @@ -590,8 +607,15 @@ export const intentIqIdSubmodule = { if ('spd' in respJson) { // server provided data - firstPartyData.spd = respJson.spd; + partnerData.spd = respJson.spd; + } + + if ('abTestUuid' in respJson) { + if ('ls' in respJson && respJson.ls === true) { + partnerData.abTestUuid = respJson.abTestUuid; + } } + if ('gpr' in respJson) { // GAM prediction reporting partnerData.gpr = respJson.gpr; @@ -599,10 +623,6 @@ export const intentIqIdSubmodule = { delete partnerData.gpr // remove prediction flag in case server doesn't provide it } - if (rrttStrtTime && rrttStrtTime > 0) { - partnerData.rrtt = Date.now() - rrttStrtTime; - } - if (respJson.data?.eids) { runtimeEids = respJson.data callback(respJson.data.eids); @@ -627,12 +647,13 @@ export const intentIqIdSubmodule = { callback(runtimeEids); } }; - rrttStrtTime = Date.now(); partnerData.wsrvcll = true; storeData(PARTNER_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); clearCountersAndStore(allowedStorage, partnerData); + rrttStrtTime = Date.now(); + const sendAjax = uh => { if (uh) url += '&uh=' + encodeURIComponent(uh); ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); @@ -654,7 +675,7 @@ export const intentIqIdSubmodule = { sendAjax(''); } }; - const respObj = {callback: resp}; + const respObj = { callback: resp }; if (runtimeEids?.eids?.length) respObj.id = runtimeEids.eids; return respObj diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index bf561649566..2465f8cbf6f 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -53,6 +53,10 @@ Please find below list of parameters that could be used in configuring Intent IQ | params.siloEnabled | Optional | Boolean | Determines if first-party data is stored in a siloed storage key. When set to `true`, first-party data is stored under a modified key that appends `_p_` plus the partner value rather than using the default storage key. The default value is `false`. | `true` | | params.groupChanged | Optional | Function | A callback that is triggered every time the user’s A/B group is set or updated. |`(group) => console.log('Group changed:', group)` | | params.chTimeout | Optional | Number | Maximum time (in milliseconds) to wait for Client Hints from the browser before sending request. Default value is `10ms` | `30` | +| params. ABTestingConfigurationSource| Optional | String | Determines how AB group will be defined. Possible values: `"IIQServer"` – group defined by IIQ server, `"percentage"` – generated group based on abPercentage, `"group"` – define group based on value provided by partner. | `IIQServer` | +| params.abPercentage | Optional | Number | Percentage for A/B testing group. Default value is `95` | `95` | +| params.group | Optional | String | Define group provided by partner, possible values: `"A"`, `"B"` | `"A"` | +| params.region | Optional | String | Optional region identifier used to automatically build server endpoints. When specified, region-specific endpoints will be used. (`gdpr`,`emea`, `apac`) | `"gdpr"` | | params.additionalParams | Optional | Array | This parameter allows sending additional custom key-value parameters with specific destination logic (sync, VR, winreport). Each custom parameter is defined as an object in the array. | `[ { parameterName: “abc”, parameterValue: 123, destination: [1,1,0] } ]` | | params.additionalParams [0].parameterName | Required | String | Name of the custom parameter. This will be sent as a query parameter. | `"abc"` | | params.additionalParams [0].parameterValue | Required | String / Number | Value to assign to the parameter. | `123` | @@ -69,6 +73,7 @@ pbjs.setConfig({ partner: 123456, // valid partner id timeoutInMillis: 500, browserBlackList: "chrome", + ABTestingConfigurationSource: 'IIQServer', callback: (data) => {...}, // your logic here groupChanged: (group) => console.log('Group is', group), domainName: "currentDomain.com", @@ -77,6 +82,8 @@ pbjs.setConfig({ sourceMetaData: "123.123.123.123", // Optional parameter sourceMetaDataExternal: 123456, // Optional parameter chTimeout: 10, // Optional parameter + abPercentage: 95, // Optional parameter + region: "gdpr", // Optional parameter additionalParams: [ // Optional parameter { parameterName: "abc", diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 2b0c55b2fb9..0eb6a74abfe 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -204,7 +204,12 @@ function buildOpenRtbImpObject(validBidRequest) { }, bidfloor: floorInfo.floor, bidfloorcur: floorInfo.currency, - pmp: buildPmpObject(validBidRequest) + pmp: buildPmpObject(validBidRequest), + ext: { + prebid: { + adunitcode: validBidRequest.adUnitCode + } + } }; } diff --git a/modules/leagueMBidAdapter.js b/modules/leagueMBidAdapter.js new file mode 100644 index 00000000000..3a0f1cdbebb --- /dev/null +++ b/modules/leagueMBidAdapter.js @@ -0,0 +1,17 @@ +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 = 'leagueM'; +const ENDPOINT = 'https://pbjs.league-m.media'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: bid => isBidRequestValid(bid, ['pid']), + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/leagueMBidAdapter.md b/modules/leagueMBidAdapter.md new file mode 100644 index 00000000000..5ef96b0d66b --- /dev/null +++ b/modules/leagueMBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: LeagueM Bidder Adapter +Module Type: LeagueM Bidder Adapter +Maintainer: prebid@league-m.com +``` + +# Description + +Module that connects to league-m.media demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'leagueM', + params: { + env: 'leagueM', + pid: 'aa8217e20131c095fe9dba67981040b0', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skippable: true + } + }, + bids: [{ + bidder: 'leagueM', + params: { + env: 'leagueM', + pid: 'aa8217e20131c095fe9dba67981040b0', + ext: {} + } + }] + } +]; +``` diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 40728c54245..34c5704155f 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -1,7 +1,8 @@ -import { logMessage, groupBy, flatten, uniques } from '../src/utils.js'; +import { uniques, flatten, deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -10,6 +11,9 @@ import { ajax } from '../src/ajax.js'; */ const BIDDER_CODE = 'limelightDigital'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_TTL = 300; +const MTYPE_MAP = { 1: BANNER, 2: VIDEO }; /** * Determines whether or not the given bid response is valid. @@ -21,7 +25,7 @@ function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency || !bid.meta.advertiserDomains) { return false; } - switch (bid.meta.mediaType) { + switch (bid.mediaType) { case BANNER: return Boolean(bid.width && bid.height && bid.ad); case VIDEO: @@ -30,12 +34,46 @@ function isBidResponseValid(bid) { return false; } +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + for (let i = 1; i <= 5; i++) { + const customValue = bidRequest.params[`custom${i}`]; + if (customValue !== undefined) { + deepSetValue(imp, `ext.c${i}`, customValue); + } + } + deepSetValue(imp, `ext.adUnitId`, bidRequest.params.adUnitId); + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + let mediaType; + if (bid.mtype) { + mediaType = MTYPE_MAP[bid.mtype]; + } + if (!mediaType && bid.ext?.mediaType) { + mediaType = bid.ext.mediaType; + } + if (!mediaType && context.imp) { + if (context.imp.banner) mediaType = BANNER; + else if (context.imp.video) mediaType = VIDEO; + } + if (mediaType) { + context.mediaType = mediaType; + } + return buildBidResponse(bid, context); + } +}); + export const spec = { code: BIDDER_CODE, aliases: [ { code: 'pll' }, { code: 'iionads', gvlid: 1358 }, - { code: 'apester' }, { code: 'adsyield' }, { code: 'tgm' }, { code: 'adtg_org' }, @@ -45,10 +83,11 @@ export const spec = { { code: 'stellorMediaRtb' }, { code: 'smootai' }, { code: 'anzuExchange' }, - { code: 'adnimation' }, { code: 'rtbdemand' }, { code: 'altstar' }, - { code: 'vaayaMedia' } + { code: 'vaayaMedia' }, + { code: 'performist' }, + { code: 'oveeo' } ], supportedMediaTypes: [BANNER, VIDEO], @@ -64,22 +103,62 @@ export const spec = { }, /** - * Make a server request from the list of BidRequests. + * Make a server request from the list of BidRequests using OpenRTB format. * - * @return ServerRequest Info describing the request to the server. + * @param {BidRequest[]} validBidRequests - Array of valid bid requests + * @param {Object} bidderRequest - The bidder request object + * @return {Object[]} Array of server requests */ buildRequests: (validBidRequests, bidderRequest) => { - let winTop; - try { - winTop = window.top; - winTop.location.toString(); - } catch (e) { - logMessage(e); - winTop = window; - } - const placements = groupBy(validBidRequests.map(bidRequest => buildPlacement(bidRequest)), 'host') - return Object.keys(placements) - .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit), bidderRequest)); + const normalizedBids = validBidRequests.map(bid => { + const adUnitType = bid.params.adUnitType || BANNER + if (!bid.mediaTypes && bid.sizes) { + if (adUnitType === BANNER) { + return { ...bid, mediaTypes: { banner: { sizes: bid.sizes } } }; + } else { + return { ...bid, mediaTypes: { video: { playerSize: bid.sizes } } }; + } + } + if (bid.mediaTypes && bid.sizes) { + const mediaTypes = { ...bid.mediaTypes }; + if (adUnitType === BANNER && mediaTypes.banner) { + mediaTypes.banner = { + ...mediaTypes.banner, + sizes: (mediaTypes.banner.sizes || []).concat(bid.sizes) + }; + } + if (adUnitType === VIDEO && mediaTypes.video) { + mediaTypes.video = { + ...mediaTypes.video, + playerSize: (mediaTypes.video.playerSize || []).concat(bid.sizes) + }; + } + return { ...bid, mediaTypes }; + } + return bid; + }); + const bidRequestsByHost = normalizedBids.reduce((groups, bid) => { + const host = bid.params.host; + groups[host] = groups[host] || []; + groups[host].push(bid); + return groups; + }, {}); + const enrichedBidderRequest = { + ...bidderRequest, + ortb2: { + ...bidderRequest.ortb2, + site: { + ...bidderRequest.ortb2?.site, + page: bidderRequest.ortb2?.site?.page || bidderRequest.refererInfo?.page + } + } + }; + + return Object.entries(bidRequestsByHost).map(([host, bids]) => ({ + method: 'POST', + url: `https://${host}/ortbhb`, + data: converter.toORTB({ bidRequests: bids, bidderRequest: enrichedBidderRequest }) + })); }, /** @@ -98,22 +177,20 @@ export const spec = { }, /** - * Unpack the response from the server into a list of bids. + * Unpack the OpenRTB 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. + * @param {ServerResponse} response - A successful response from the server + * @param {Object} request - The request object that was sent + * @return {Bid[]} An array of bids */ - interpretResponse: (serverResponse, bidRequest) => { - const bidResponses = []; - const serverBody = serverResponse.body; - const len = serverBody.length; - for (let i = 0; i < len; i++) { - const bidResponse = serverBody[i]; - if (isBidResponseValid(bidResponse)) { - bidResponses.push(bidResponse); - } + interpretResponse: (response, request) => { + if (!response.body) { + return []; } - return bidResponses; + return converter.fromORTB({ + response: response.body, + request: request.data + }).bids.filter(bid => isBidResponseValid(bid)); }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { @@ -135,63 +212,3 @@ export const spec = { }; registerBidder(spec); - -function buildRequest(winTop, host, adUnits, bidderRequest) { - return { - method: 'POST', - url: `https://${host}/hb`, - data: { - secure: (location.protocol === 'https:'), - 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 - } - } -} - -function buildPlacement(bidRequest) { - let sizes; - if (bidRequest.mediaTypes) { - switch (bidRequest.params.adUnitType) { - case BANNER: - if (bidRequest.mediaTypes.banner && bidRequest.mediaTypes.banner.sizes) { - sizes = bidRequest.mediaTypes.banner.sizes; - } - break; - case VIDEO: - if (bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.playerSize) { - sizes = [bidRequest.mediaTypes.video.playerSize]; - } - break; - } - } - sizes = (sizes || []).concat(bidRequest.sizes || []); - return { - host: bidRequest.params.host, - adUnit: { - id: bidRequest.params.adUnitId, - bidId: bidRequest.bidId, - transactionId: bidRequest.ortb2Imp?.ext?.tid, - sizes: sizes.map(size => { - return { - width: size[0], - height: size[1] - } - }), - type: bidRequest.params.adUnitType.toUpperCase(), - ortb2Imp: bidRequest.ortb2Imp, - publisherId: bidRequest.params.publisherId, - userIdAsEids: bidRequest.userIdAsEids, - supplyChain: bidRequest?.ortb2?.source?.ext?.schain, - custom1: bidRequest.params.custom1, - custom2: bidRequest.params.custom2, - custom3: bidRequest.params.custom3, - custom4: bidRequest.params.custom4, - custom5: bidRequest.params.custom5 - } - } -} diff --git a/modules/liveIntentAnalyticsAdapter.js b/modules/liveIntentAnalyticsAdapter.js index 9a3bd53945e..1a548bfcd9c 100644 --- a/modules/liveIntentAnalyticsAdapter.js +++ b/modules/liveIntentAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { ajax } from '../src/ajax.js'; -import { generateUUID, isNumber } from '../src/utils.js'; +import { generateUUID, isNumber, parseSizesInput } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; @@ -41,6 +41,13 @@ function handleAuctionInitEvent(auctionInitEvent) { // dependeing on the result of rolling the dice outside of Prebid. const partnerIdFromAnalyticsLabels = auctionInitEvent.analyticsLabels?.partnerId; + const asz = auctionInitEvent?.adUnits.reduce((acc, adUnit) => + acc.concat( + parseSizesInput(adUnit?.mediaTypes?.banner?.sizes), + parseSizesInput(adUnit?.mediaTypes?.video?.playerSize) + ), [] + ) + const data = { id: generateUUID(), aid: auctionInitEvent.auctionId, @@ -51,7 +58,8 @@ function handleAuctionInitEvent(auctionInitEvent) { tr: window.liTreatmentRate, me: encodeBoolean(window.liModuleEnabled), liip: encodeBoolean(liveIntentIdsPresent), - aun: auctionInitEvent?.adUnits?.length || 0 + aun: auctionInitEvent?.adUnits?.length || 0, + asz: asz.join(',') }; const filteredData = ignoreUndefined(data); sendData('auction-init', filteredData); @@ -82,7 +90,8 @@ function handleBidWonEvent(bidWonEvent) { rts: bidWonEvent.responseTimestamp, tr: window.liTreatmentRate, me: encodeBoolean(window.liModuleEnabled), - liip: encodeBoolean(liveIntentIdsPresent) + liip: encodeBoolean(liveIntentIdsPresent), + asz: bidWonEvent.width + 'x' + bidWonEvent.height }; const filteredData = ignoreUndefined(data); diff --git a/modules/locIdSystem.js b/modules/locIdSystem.js new file mode 100644 index 00000000000..5e06e29b302 --- /dev/null +++ b/modules/locIdSystem.js @@ -0,0 +1,676 @@ +/** + * This file is licensed under the Apache 2.0 license. + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * This module adds LocID to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/locIdSystem + * @requires module:modules/userId + */ + +import { logWarn, logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { gppDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { VENDORLESS_GVLID } from '../src/consentHandler.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; + +const MODULE_NAME = 'locId'; +const LOG_PREFIX = 'LocID:'; +const DEFAULT_TIMEOUT_MS = 800; +const DEFAULT_EID_SOURCE = 'locid.com'; +// EID atype: 1 = AdCOM AgentTypeWeb (agent type for web environments) +const DEFAULT_EID_ATYPE = 1; +const MAX_ID_LENGTH = 512; +const MAX_CONNECTION_IP_LENGTH = 64; +const DEFAULT_IP_CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours +const IP_CACHE_SUFFIX = '_ip'; + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +/** + * Normalizes privacy mode config to a boolean flag. + * Supports both requirePrivacySignals (boolean) and privacyMode (string enum). + * + * Precedence: requirePrivacySignals (if true) takes priority over privacyMode. + * - privacyMode is the preferred high-level setting for new integrations. + * - requirePrivacySignals exists for backwards compatibility with integrators + * who prefer a simple boolean. + * + * @param {Object} params - config.params + * @returns {boolean} true if privacy signals are required, false otherwise + */ +function shouldRequirePrivacySignals(params) { + // requirePrivacySignals=true takes precedence (backwards compatibility) + if (params?.requirePrivacySignals === true) { + return true; + } + if (params?.privacyMode === 'requireSignals') { + return true; + } + // Default: allowWithoutSignals + return false; +} + +function getUspConsent(consentData) { + if (consentData && consentData.usp != null) { + return consentData.usp; + } + return consentData?.uspConsent; +} + +function getGppConsent(consentData) { + if (consentData && consentData.gpp != null) { + return consentData.gpp; + } + return consentData?.gppConsent; +} + +/** + * Checks if any privacy signals are present in consentData or data handlers. + * + * IMPORTANT: gdprApplies alone does NOT count as a privacy signal. + * A publisher may set gdprApplies=true without having a CMP installed. + * We only consider GDPR signals "present" when actual consent framework + * artifacts exist (consentString, vendorData). This supports LI-based + * operation where no TCF consent string is required. + * + * "Signals present" means ANY of the following are available: + * - consentString or gdpr.consentString (indicates CMP provided framework data) + * - vendorData or gdpr.vendorData (indicates CMP provided vendor data) + * - usp or uspConsent (US Privacy string) + * - gpp or gppConsent (GPP consent data) + * - Data from gppDataHandler or uspDataHandler + * + * @param {Object} consentData - The consent data object passed to getId + * @returns {boolean} true if any privacy signals are present + */ +function hasPrivacySignals(consentData) { + // Check GDPR-related signals (flat and nested) + // NOTE: gdprApplies alone is NOT a signal - it just indicates jurisdiction. + // A signal requires actual CMP artifacts (consentString or vendorData). + if (consentData?.consentString || consentData?.gdpr?.consentString) { + return true; + } + if (consentData?.vendorData || consentData?.gdpr?.vendorData) { + return true; + } + + // Check USP consent + const uspConsent = getUspConsent(consentData); + if (uspConsent) { + return true; + } + + // Check GPP consent + const gppConsent = getGppConsent(consentData); + if (gppConsent) { + return true; + } + + // Check data handlers + const uspFromHandler = uspDataHandler.getConsentData(); + if (uspFromHandler) { + return true; + } + + const gppFromHandler = gppDataHandler.getConsentData(); + if (gppFromHandler) { + return true; + } + + return false; +} + +function isValidId(id) { + return typeof id === 'string' && id.trim().length > 0 && id.length <= MAX_ID_LENGTH; +} + +function isValidConnectionIp(ip) { + return typeof ip === 'string' && ip.length > 0 && ip.length <= MAX_CONNECTION_IP_LENGTH; +} + +function normalizeStoredId(storedId) { + if (!storedId) { + return null; + } + if (typeof storedId === 'string') { + return null; + } + if (typeof storedId === 'object') { + // Preserve explicit null for id (means "empty tx_cloc, valid cached response"). + // 'id' in storedId is needed because ?? treats null as nullish and would + // incorrectly fall through to tx_cloc. + const hasExplicitId = 'id' in storedId; + const id = hasExplicitId ? storedId.id : (storedId.tx_cloc ?? null); + const connectionIp = storedId.connectionIp ?? storedId.connection_ip; + return { ...storedId, id, connectionIp }; + } + return null; +} + +/** + * Checks privacy framework signals. Returns true if ID operations are allowed. + * + * LocID operates under Legitimate Interest and does not require a TCF consent + * string when no privacy framework is present. When privacy signals exist, + * framework processing restrictions are enforced. + * + * @param {Object} consentData - The consent data object from Prebid + * @param {Object} params - config.params for privacy mode settings + * @returns {boolean} true if ID operations are allowed + */ +function hasValidConsent(consentData, params) { + const requireSignals = shouldRequirePrivacySignals(params); + const signalsPresent = hasPrivacySignals(consentData); + + // B) If privacy signals are NOT present + if (!signalsPresent) { + if (requireSignals) { + logWarn(LOG_PREFIX, 'Privacy signals required but none present'); + return false; + } + // Default: allow operation without privacy signals (LI-based operation) + return true; + } + + // A) Privacy signals ARE present - enforce applicable restrictions + // + // Note: privacy signals can come from GDPR, USP, or GPP. GDPR checks only + // apply when GDPR is flagged AND CMP artifacts (consentString/vendorData) + // are present. gdprApplies alone does not trigger GDPR enforcement. + + // Check GDPR - support both flat and nested shapes + const gdprApplies = consentData?.gdprApplies === true || consentData?.gdpr?.gdprApplies === true; + const consentString = consentData?.consentString || consentData?.gdpr?.consentString; + const vendorData = consentData?.vendorData || consentData?.gdpr?.vendorData; + const gdprCmpArtifactsPresent = !!(consentString || vendorData); + + if (gdprApplies && gdprCmpArtifactsPresent) { + // When GDPR applies AND we have CMP signals, require consentString + if (!consentString || consentString.length === 0) { + logWarn(LOG_PREFIX, 'GDPR framework data missing consent string'); + return false; + } + } + + // Check USP for processing restriction + const uspData = getUspConsent(consentData) ?? uspDataHandler.getConsentData(); + if (uspData && uspData.length >= 3 && uspData.charAt(2) === 'Y') { + logWarn(LOG_PREFIX, 'US Privacy framework processing restriction detected'); + return false; + } + + // Check GPP for processing restriction + const gppData = getGppConsent(consentData) ?? gppDataHandler.getConsentData(); + if (gppData?.applicableSections?.includes(7) && + gppData?.parsedSections?.usnat?.KnownChildSensitiveDataConsents?.includes(1)) { + logWarn(LOG_PREFIX, 'GPP usnat KnownChildSensitiveDataConsents processing restriction detected'); + return false; + } + + return true; +} + +function parseEndpointResponse(response) { + if (!response) { + return null; + } + try { + return typeof response === 'string' ? JSON.parse(response) : response; + } catch (e) { + logError(LOG_PREFIX, 'Error parsing endpoint response:', e.message); + return null; + } +} + +/** + * Extracts LocID from endpoint response. + * Only tx_cloc is accepted. + */ +function extractLocIdFromResponse(parsed) { + if (!parsed) return null; + + if (isValidId(parsed.tx_cloc)) { + return parsed.tx_cloc; + } + + logWarn(LOG_PREFIX, 'Could not extract valid tx_cloc from response'); + return null; +} + +function extractConnectionIp(parsed) { + if (!parsed) { + return null; + } + const connectionIp = parsed.connection_ip ?? parsed.connectionIp; + return isValidConnectionIp(connectionIp) ? connectionIp : null; +} + +function getIpCacheKey(config) { + const baseName = config?.storage?.name || '_locid'; + return config?.params?.ipCacheName || (baseName + IP_CACHE_SUFFIX); +} + +function getIpCacheTtlMs(config) { + const ttl = config?.params?.ipCacheTtlMs; + return (typeof ttl === 'number' && ttl > 0) ? ttl : DEFAULT_IP_CACHE_TTL_MS; +} + +function readIpCache(config) { + try { + const key = getIpCacheKey(config); + const raw = storage.getDataFromLocalStorage(key); + if (!raw) return null; + const entry = JSON.parse(raw); + if (!entry || typeof entry !== 'object') return null; + if (!isValidConnectionIp(entry.ip)) return null; + if (typeof entry.expiresAt === 'number' && Date.now() > entry.expiresAt) return null; + return entry; + } catch (e) { + logWarn(LOG_PREFIX, 'Error reading IP cache:', e.message); + return null; + } +} + +function writeIpCache(config, ip) { + if (!isValidConnectionIp(ip)) return; + try { + const key = getIpCacheKey(config); + const nowMs = Date.now(); + const ttlMs = getIpCacheTtlMs(config); + const entry = { + ip: ip, + fetchedAt: nowMs, + expiresAt: nowMs + ttlMs + }; + storage.setDataInLocalStorage(key, JSON.stringify(entry)); + } catch (e) { + logWarn(LOG_PREFIX, 'Error writing IP cache:', e.message); + } +} + +/** + * Parses an IP response from an IP-only endpoint. + * Supports JSON ({ip: "..."}, {connection_ip: "..."}) and plain text IP. + */ +function parseIpResponse(response) { + if (!response) return null; + + if (typeof response === 'string') { + const trimmed = response.trim(); + if (trimmed.charAt(0) === '{') { + try { + const parsed = JSON.parse(trimmed); + const ip = parsed.ip || parsed.connection_ip || parsed.connectionIp; + return isValidConnectionIp(ip) ? ip : null; + } catch (e) { + // Not valid JSON, try as plain text + } + } + return isValidConnectionIp(trimmed) ? trimmed : null; + } + + if (typeof response === 'object') { + const ip = response.ip || response.connection_ip || response.connectionIp; + return isValidConnectionIp(ip) ? ip : null; + } + + return null; +} + +/** + * Checks if a stored tx_cloc entry is valid for reuse. + * Accepts both valid id strings AND null (empty tx_cloc is a valid cached result). + */ +function isStoredEntryReusable(normalizedStored, currentIp) { + if (!normalizedStored || !isValidConnectionIp(normalizedStored.connectionIp)) { + return false; + } + if (isExpired(normalizedStored)) { + return false; + } + if (currentIp && normalizedStored.connectionIp !== currentIp) { + return false; + } + // id must be either a valid string or explicitly null (empty tx_cloc) + return normalizedStored.id === null || isValidId(normalizedStored.id); +} + +function getExpiresAt(config, nowMs) { + const expiresDays = config?.storage?.expires; + if (typeof expiresDays !== 'number' || expiresDays <= 0) { + return undefined; + } + return nowMs + (expiresDays * 24 * 60 * 60 * 1000); +} + +function buildStoredId(id, connectionIp, config) { + const nowMs = Date.now(); + return { + id, + connectionIp, + createdAt: nowMs, + updatedAt: nowMs, + expiresAt: getExpiresAt(config, nowMs) + }; +} + +function isExpired(storedEntry) { + return typeof storedEntry?.expiresAt === 'number' && Date.now() > storedEntry.expiresAt; +} + +/** + * Builds the request URL, appending altId if configured. + * Preserves URL fragments by appending query params before the hash. + */ +function buildRequestUrl(endpoint, altId) { + if (!altId) { + return endpoint; + } + + // Split on hash to preserve fragment + const hashIndex = endpoint.indexOf('#'); + let base = endpoint; + let fragment = ''; + + if (hashIndex !== -1) { + base = endpoint.substring(0, hashIndex); + fragment = endpoint.substring(hashIndex); + } + + const separator = base.includes('?') ? '&' : '?'; + return `${base}${separator}alt_id=${encodeURIComponent(altId)}${fragment}`; +} + +/** + * Fetches LocID from the configured endpoint (GET only). + */ +function fetchLocIdFromEndpoint(config, callback) { + const params = config?.params || {}; + const endpoint = params.endpoint; + const timeoutMs = params.timeoutMs || DEFAULT_TIMEOUT_MS; + + if (!endpoint) { + logError(LOG_PREFIX, 'No endpoint configured'); + callback(undefined); + return; + } + + const requestUrl = buildRequestUrl(endpoint, params.altId); + + const requestOptions = { + method: 'GET', + contentType: 'application/json', + withCredentials: params.withCredentials === true + }; + + // Add x-api-key header if apiKey is configured + if (params.apiKey) { + requestOptions.customHeaders = { + 'x-api-key': params.apiKey + }; + } + + let callbackFired = false; + const safeCallback = (result) => { + if (!callbackFired) { + callbackFired = true; + callback(result); + } + }; + + const onSuccess = (response) => { + const parsed = parseEndpointResponse(response); + if (!parsed) { + safeCallback(undefined); + return; + } + const connectionIp = extractConnectionIp(parsed); + if (!connectionIp) { + logWarn(LOG_PREFIX, 'Missing or invalid connection_ip in response'); + safeCallback(undefined); + return; + } + // tx_cloc may be null (empty/missing for this IP) -- this is a valid cacheable result. + // connection_ip is always required. + const locId = extractLocIdFromResponse(parsed); + writeIpCache(config, connectionIp); + safeCallback(buildStoredId(locId, connectionIp, config)); + }; + + const onError = (error) => { + logWarn(LOG_PREFIX, 'Request failed:', error); + safeCallback(undefined); + }; + + try { + const ajax = ajaxBuilder(timeoutMs); + ajax(requestUrl, { success: onSuccess, error: onError }, null, requestOptions); + } catch (e) { + logError(LOG_PREFIX, 'Error initiating request:', e.message); + safeCallback(undefined); + } +} + +/** + * Fetches the connection IP from a separate lightweight endpoint (GET only). + * Callback receives the IP string on success or null on failure. + */ +function fetchIpFromEndpoint(config, callback) { + const params = config?.params || {}; + const ipEndpoint = params.ipEndpoint; + const timeoutMs = params.timeoutMs || DEFAULT_TIMEOUT_MS; + + if (!ipEndpoint) { + callback(null); + return; + } + + let callbackFired = false; + const safeCallback = (result) => { + if (!callbackFired) { + callbackFired = true; + callback(result); + } + }; + + const onSuccess = (response) => { + const ip = parseIpResponse(response); + safeCallback(ip); + }; + + const onError = (error) => { + logWarn(LOG_PREFIX, 'IP endpoint request failed:', error); + safeCallback(null); + }; + + try { + const ajax = ajaxBuilder(timeoutMs); + const requestOptions = { + method: 'GET', + withCredentials: params.withCredentials === true + }; + if (params.apiKey) { + requestOptions.customHeaders = { + 'x-api-key': params.apiKey + }; + } + ajax(ipEndpoint, { success: onSuccess, error: onError }, null, requestOptions); + } catch (e) { + logError(LOG_PREFIX, 'Error initiating IP request:', e.message); + safeCallback(null); + } +} + +export const locIdSubmodule = { + name: MODULE_NAME, + aliasName: 'locid', + gvlid: VENDORLESS_GVLID, + + /** + * Decode stored value into userId object. + */ + decode(value) { + if (!value || typeof value !== 'object') { + return undefined; + } + const id = value?.id ?? value?.tx_cloc; + const connectionIp = value?.connectionIp ?? value?.connection_ip; + if (isValidId(id) && isValidConnectionIp(connectionIp)) { + return { locId: id }; + } + return undefined; + }, + + /** + * Get the LocID from endpoint. + * Returns {id} for sync or {callback} for async per Prebid patterns. + * + * Two-tier cache: IP cache (4h default) and tx_cloc cache (7d default). + * IP is refreshed more frequently to detect network changes while keeping + * tx_cloc stable for its full cache period. + */ + getId(config, consentData, storedId) { + const params = config?.params || {}; + + // Check privacy restrictions first + if (!hasValidConsent(consentData, params)) { + return undefined; + } + + const normalizedStored = normalizeStoredId(storedId); + const cachedIp = readIpCache(config); + + // Step 1: IP cache is valid -- check if tx_cloc matches + if (cachedIp) { + if (isStoredEntryReusable(normalizedStored, cachedIp.ip)) { + return { id: normalizedStored }; + } + // IP cached but tx_cloc missing, expired, or IP mismatch -- full fetch + return { + callback: (callback) => { + fetchLocIdFromEndpoint(config, callback); + } + }; + } + + // Step 2: IP cache expired or missing + if (params.ipEndpoint) { + // Two-call optimization: lightweight IP check first + return { + callback: (callback) => { + fetchIpFromEndpoint(config, (freshIp) => { + if (!freshIp) { + // IP fetch failed; fall back to main endpoint + fetchLocIdFromEndpoint(config, callback); + return; + } + writeIpCache(config, freshIp); + // Check if stored tx_cloc matches the fresh IP + if (isStoredEntryReusable(normalizedStored, freshIp)) { + callback(normalizedStored); + return; + } + // IP changed or no valid tx_cloc -- full fetch + fetchLocIdFromEndpoint(config, callback); + }); + } + }; + } + + // Step 3: No ipEndpoint configured -- call main endpoint to refresh IP. + // Only update tx_cloc if IP changed or tx_cloc cache expired. + return { + callback: (callback) => { + fetchLocIdFromEndpoint(config, (freshEntry) => { + if (!freshEntry) { + callback(undefined); + return; + } + // Honor empty tx_cloc: if the server returned null, use the fresh + // entry so stale identifiers are cleared (cached as id: null). + if (freshEntry.id === null) { + callback(freshEntry); + return; + } + // IP is already cached by fetchLocIdFromEndpoint's onSuccess. + // Check if we should preserve the existing tx_cloc (avoid churning it). + if (normalizedStored?.id !== null && isStoredEntryReusable(normalizedStored, freshEntry.connectionIp)) { + callback(normalizedStored); + return; + } + // IP changed or tx_cloc expired/missing -- use fresh entry + callback(freshEntry); + }); + } + }; + }, + + /** + * Extend existing LocID. + * Accepts id: null (empty tx_cloc) as a valid cached result. + * If IP cache is missing/expired/mismatched, return a callback to refresh. + */ + extendId(config, consentData, storedId) { + const normalizedStored = normalizeStoredId(storedId); + if (!normalizedStored || !isValidConnectionIp(normalizedStored.connectionIp)) { + return undefined; + } + // Accept both valid id strings AND null (empty tx_cloc is a valid cached result) + if (normalizedStored.id !== null && !isValidId(normalizedStored.id)) { + return undefined; + } + if (isExpired(normalizedStored)) { + return undefined; + } + if (!hasValidConsent(consentData, config?.params)) { + return undefined; + } + const refreshInSeconds = config?.storage?.refreshInSeconds; + if (typeof refreshInSeconds === 'number' && refreshInSeconds > 0) { + const createdAt = normalizedStored.createdAt; + if (typeof createdAt !== 'number') { + return undefined; + } + const refreshAfterMs = refreshInSeconds * 1000; + if (Date.now() - createdAt >= refreshAfterMs) { + return undefined; + } + } + // Check IP cache -- if expired/missing or IP changed, trigger re-fetch + const cachedIp = readIpCache(config); + if (!cachedIp || cachedIp.ip !== normalizedStored.connectionIp) { + return { + callback: (callback) => { + fetchLocIdFromEndpoint(config, callback); + } + }; + } + return { id: normalizedStored }; + }, + + /** + * EID configuration following standard Prebid shape. + */ + eids: { + locId: { + source: DEFAULT_EID_SOURCE, + atype: DEFAULT_EID_ATYPE, + getValue: function (data) { + if (typeof data === 'string') { + return data; + } + if (!data || typeof data !== 'object') { + return undefined; + } + return data?.id ?? data?.tx_cloc ?? data?.locId ?? data?.locid; + } + } + } +}; + +submodule('userId', locIdSubmodule); diff --git a/modules/locIdSystem.md b/modules/locIdSystem.md new file mode 100644 index 00000000000..6ffe02fe841 --- /dev/null +++ b/modules/locIdSystem.md @@ -0,0 +1,250 @@ +# LocID User ID Submodule + +## Overview + +The LocID User ID submodule retrieves a LocID from a configured first-party endpoint, honors applicable privacy framework processing restrictions when present, persists the identifier using Prebid's storage framework, and exposes the ID to bidders via the standard EIDs interface. + +LocID is a geospatial identifier provided by Digital Envoy. The endpoint is a publisher-controlled, first-party or on-premises service operated by the publisher, GrowthCode, or Digital Envoy. The endpoint derives location information server-side. The browser module does not transmit IP addresses. + +## Configuration + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'locId', + params: { + endpoint: 'https://id.example.com/locid', + ipEndpoint: 'https://id.example.com/ip' // optional: lightweight IP-only check + }, + storage: { + type: 'html5', + name: '_locid', + expires: 7 + } + }] + } +}); +``` + +## Parameters + +| Parameter | Type | Required | Default | Description | +| ----------------------- | ------- | -------- | ----------------------- | ----------------------------------------------------------------------------- | +| `endpoint` | String | Yes | – | First-party LocID endpoint (see Endpoint Requirements below) | +| `ipEndpoint` | String | No | – | Separate endpoint returning the connection IP (see IP Change Detection below) | +| `ipCacheTtlMs` | Number | No | `14400000` (4h) | TTL for the IP cache entry in milliseconds | +| `ipCacheName` | String | No | `{storage.name}_ip` | localStorage key for the IP cache (auto-derived if not set) | +| `altId` | String | No | – | Alternative identifier appended as `?alt_id=` query param | +| `timeoutMs` | Number | No | `800` | Request timeout in milliseconds | +| `withCredentials` | Boolean | No | `false` | Whether to include credentials on the request | +| `apiKey` | String | No | – | API key sent as `x-api-key` header on `endpoint` and `ipEndpoint` requests | +| `requirePrivacySignals` | Boolean | No | `false` | If `true`, requires privacy signals to be present | +| `privacyMode` | String | No | `'allowWithoutSignals'` | `'allowWithoutSignals'` or `'requireSignals'` | + +**Note on privacy configuration:** `privacyMode` is the preferred high-level setting for new integrations. `requirePrivacySignals` exists for backwards compatibility with integrators who prefer a simple boolean. If `requirePrivacySignals: true` is set, it takes precedence. + +### Endpoint Requirements + +The `endpoint` parameter must point to a **first-party proxy** or **on-premises service**—not the raw LocID Encrypt API directly. + +The LocID Encrypt API (`GET /encrypt?ip=&alt_id=`) requires the client IP address as a parameter. Since browsers cannot reliably determine their own public IP, a server-side proxy is required to: + +1. Receive the request from the browser +2. Extract the client IP from the incoming connection +3. Forward the request to the LocID Encrypt API with the IP injected +4. Return the response with `tx_cloc` and `connection_ip` to the browser (any `stable_cloc` is ignored client-side) + +This architecture ensures the browser never transmits IP addresses and the LocID service receives accurate location data. + +If you configure `altId`, the module appends it as `?alt_id=` to the endpoint URL. Your proxy can then forward this to the LocID API. + +### CORS Configuration + +If your endpoint is on a different origin or you set `withCredentials: true`, ensure your server returns appropriate CORS headers: + +```http +Access-Control-Allow-Origin: +Access-Control-Allow-Credentials: true +``` + +When using `withCredentials`, the server cannot use `Access-Control-Allow-Origin: *`; it must specify the exact origin. + +### Storage Configuration + +| Parameter | Required | Description | +| --------- | -------- | ---------------- | +| `type` | Yes | `'html5'` | +| `name` | Yes | Storage key name | +| `expires` | No | TTL in days | + +### Stored Value Format + +The module stores a structured object (rather than a raw string) so it can track IP-aware metadata: + +```json +{ + "id": "", + "connectionIp": "203.0.113.42", + "createdAt": 1738147200000, + "updatedAt": 1738147200000, + "expiresAt": 1738752000000 +} +``` + +When the endpoint returns a valid `connection_ip` but no usable `tx_cloc` (`null`, missing, empty, or whitespace-only), `id` is stored as `null`. This caches the "no location for this IP" result for the full cache period without re-fetching. The `decode()` function returns `undefined` for `null` IDs, so no EID is emitted in bid requests. + +**Important:** String-only stored values are treated as invalid and are not emitted. + +### IP Cache Format + +The module maintains a separate IP cache entry in localStorage (default key: `{storage.name}_ip`) with a shorter TTL (default 4 hours): + +```json +{ + "ip": "203.0.113.42", + "fetchedAt": 1738147200000, + "expiresAt": 1738161600000 +} +``` + +This entry is managed by the module directly via Prebid's `storageManager` and is independent of the framework-managed tx_cloc cache. + +## Operation Flow + +The module uses a two-tier cache: an IP cache (default 4-hour TTL) and a tx_cloc cache (TTL defined by `storage.expires`). The IP is refreshed more frequently to detect network changes while keeping tx_cloc stable for its full cache period. + +1. The module checks the IP cache for a current connection IP. +2. If the IP cache is valid, the module compares it against the stored tx_cloc entry's `connectionIp`. +3. If the IPs match and the tx_cloc entry is not expired, the cached tx_cloc is reused (even if `null`). +4. If the IP cache is expired or missing and `ipEndpoint` is configured, the module calls `ipEndpoint` to get the current IP, then compares with the stored tx_cloc. If the IPs match, the tx_cloc is reused without calling the main endpoint. +5. If the IPs differ, or the tx_cloc is expired/missing, or `ipEndpoint` is not configured, the module calls the main endpoint to get a fresh tx_cloc and connection IP. +6. The endpoint response may include a `null`, empty, whitespace-only, or missing `tx_cloc` (indicating no location for this IP). This is cached as `id: null` for the full cache period, and overrides any previously stored non-null ID for that same IP. +7. Both the IP cache and tx_cloc cache are updated after each endpoint call. +8. The ID is included in bid requests via the EIDs array. Entries with `null` tx_cloc are omitted from bid requests. + +## Endpoint Response Requirements + +The proxy must return: + +```json +{ + "tx_cloc": "", + "connection_ip": "203.0.113.42" +} +``` + +Notes: + +- `connection_ip` is always required. If missing, the entire response is treated as a failure. +- `tx_cloc` may be `null`, missing, empty, or whitespace-only when no location is available for the IP. This is a valid response and will be cached as `id: null` for the configured cache period. +- `tx_cloc` is the only value the browser module will store/transmit. +- `stable_cloc` may exist in proxy responses for server-side caching, but the client ignores it. + +## IP Change Detection + +The module uses a two-tier cache to detect IP changes without churning the tx_cloc identifier: + +- **IP cache** (default 4-hour TTL): Tracks the current connection IP. Stored in a separate localStorage key (`{storage.name}_ip`). +- **tx_cloc cache** (`storage.expires`): Stores the LocID. Managed by Prebid's userId framework. + +When the IP cache expires, the module refreshes the IP. If the IP is unchanged and the tx_cloc cache is still valid, the existing tx_cloc is reused without calling the main endpoint. + +### ipEndpoint (optional) + +When `ipEndpoint` is configured, the module calls it for lightweight IP-only checks. This avoids a full tx_cloc API call when only the IP needs refreshing. The endpoint should return the connection IP in one of these formats: + +- JSON: `{"ip": "203.0.113.42"}` or `{"connection_ip": "203.0.113.42"}` +- Plain text: `203.0.113.42` + +If `apiKey` is configured, the `x-api-key` header is included on `ipEndpoint` requests using the same `customHeaders` mechanism as the main endpoint. + +When `ipEndpoint` is not configured, the module falls back to calling the main endpoint to refresh the IP, but only updates the stored tx_cloc when the IP has changed or the tx_cloc cache has expired. In this mode, IP changes are only detected when the IP cache is refreshed (for example when it expires and `extendId()` returns a refresh callback); there is no separate lightweight proactive IP probe. + +### Prebid Refresh Triggers + +When `storage.refreshInSeconds` is set, the module will reuse the cached ID until `createdAt + refreshInSeconds`; once due (or if `createdAt` is missing), `extendId()` returns `undefined` to indicate the cached ID should not be reused. The `extendId()` method also checks the IP cache: if the IP cache is expired or missing, or if the cached IP differs from the stored tx_cloc's IP, it returns a callback that refreshes via the main endpoint. This enforces the IP cache TTL (`ipCacheTtlMs`) even when the tx_cloc cache has not expired. + +## Consent Handling + +LocID operates under Legitimate Interest (LI). By default, the module proceeds when no privacy framework signals are present. When privacy signals exist, they are enforced. Privacy frameworks can only stop LocID via global processing restrictions; they do not enable it. +For TCF integration, the module declares Prebid's vendorless GVL marker so purpose-level enforcement applies without vendor-ID checks. + +### Legal Basis and IP-Based Identifiers + +LocID is derived from IP-based geolocation. Because IP addresses are transient and shared, there is no meaningful IP-level choice to express. Privacy frameworks are only consulted to honor rare, publisher- or regulator-level instructions to stop all processing. When such a global processing restriction is signaled, LocID respects it by returning `undefined`. + +### Default Behavior (allowWithoutSignals) + +- **No privacy signals present**: Module proceeds and fetches the ID +- **Privacy signals present**: Enforcement rules apply (see below) + +### Strict Mode (requireSignals) + +Set `requirePrivacySignals: true` or `privacyMode: 'requireSignals'` to require privacy signals: + +```javascript +params: { + endpoint: 'https://id.example.com/locid', + requirePrivacySignals: true +} +``` + +In strict mode, the module returns `undefined` if no privacy signals are present. + +### Privacy Signal Enforcement + +When privacy signals **are** present, the module does not fetch or return an ID if any of the following apply: + +- GDPR applies and vendorData is present, but consentString is missing or empty +- The US Privacy string indicates a global processing restriction (third character is 'Y') +- GPP signals indicate an applicable processing restriction + +When GDPR applies and `consentString` is present, the module proceeds unless a framework processing restriction is signaled. + +### Privacy Signals Detection + +The module considers privacy signals "present" if any of the following exist: + +- `consentString` (TCF consent string from CMP) +- `vendorData` (TCF vendor data from CMP) +- `usp` or `uspConsent` (US Privacy string) +- `gpp` or `gppConsent` (GPP consent data) +- Data from `uspDataHandler` or `gppDataHandler` + +**Important:** `gdprApplies` alone does NOT constitute a privacy signal. A publisher may indicate GDPR jurisdiction without having a CMP installed. TCF framework data is only required when actual CMP artifacts (`consentString` or `vendorData`) are present. This supports Legitimate Interest-based operation in deployments without a full TCF implementation. + +## EID Output + +When available, the LocID is exposed as: + +```javascript +{ + source: "locid.com", + uids: [{ + id: "", + atype: 1 // AdCOM AgentTypeWeb + }] +} +``` + +## Identifier Type + +- **`atype: 1`** — The AdCOM agent type for web (`AgentTypeWeb`). This is used in EID emission. + +`atype` is an OpenRTB agent type (environment), not an IAB GVL vendor ID. + +## Debugging + +```javascript +pbjs.getUserIds().locId +pbjs.refreshUserIds() +localStorage.getItem('_locid') +localStorage.getItem('_locid_ip') // IP cache entry +``` + +## Validation Checklist + +- [ ] EID is present in bid requests when no processing restriction is signaled +- [ ] No network request occurs when a global processing restriction is signaled +- [ ] Stored IDs are reused across page loads diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index 3d7598e1ca9..a1d333ab0af 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -1,8 +1,9 @@ import {getDNT} from '../libraries/dnt/index.js'; -import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, parseGPTSingleSizeArrayToRtbSize, isEmpty } from '../src/utils.js'; +import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, parseGPTSingleSizeArrayToRtbSize } 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 { buildNativeRequest, parseNativeResponse } from '../libraries/nativeAssetsUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -27,88 +28,6 @@ const BIDDER_CODE = 'mediaforce'; const GVLID = 671; const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid'; const TEST_ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid?debug_key=abcdefghijklmnop'; -const NATIVE_ID_MAP = {}; -const NATIVE_PARAMS = { - title: { - id: 1, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - cta: { - id: 6, - type: 12, - name: 'data' - }, - body2: { - id: 7, - name: 'data', - type: 10 - }, - rating: { - id: 8, - name: 'data', - type: 3 - }, - likes: { - id: 9, - name: 'data', - type: 4 - }, - downloads: { - id: 10, - name: 'data', - type: 5 - }, - displayUrl: { - id: 11, - name: 'data', - type: 11 - }, - price: { - id: 12, - name: 'data', - type: 6 - }, - salePrice: { - id: 13, - name: 'data', - type: 7 - }, - address: { - id: 14, - name: 'data', - type: 9 - }, - phone: { - id: 15, - name: 'data', - type: 8 - } -}; - -Object.keys(NATIVE_PARAMS).forEach((key) => { - NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key; -}); - const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const DEFAULT_CURRENCY = 'USD' @@ -175,7 +94,7 @@ export const spec = { validImp = true; break; case NATIVE: - impObj.native = createNativeRequest(bid); + impObj.native = buildNativeRequest(bid.nativeParams); validImp = true; break; case VIDEO: @@ -275,7 +194,7 @@ export const spec = { adm = null; } if (ext?.native) { - bid.native = parseNative(ext.native); + bid.native = parseNativeResponse(ext.native); bid.mediaType = NATIVE; } else if (adm?.trim().startsWith(' { - const {id, img, data, title} = asset; - const key = NATIVE_ID_MAP[id]; - if (key) { - if (!isEmpty(title)) { - result.title = title.text - } else if (!isEmpty(img)) { - result[key] = { - url: img.url, - height: img.h, - width: img.w - } - } else if (!isEmpty(data)) { - result[key] = data.value; - } - } - }); - - return result; -} - -function createNativeRequest(bid) { - const assets = []; - if (bid.nativeParams) { - Object.keys(bid.nativeParams).forEach((key) => { - if (NATIVE_PARAMS[key]) { - const {name, type, id} = NATIVE_PARAMS[key]; - const assetObj = type ? {type} : {}; - let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key]; - if (len) { - assetObj.len = len; - } - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - const wmin = aRatios.min_width || 0; - const hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - assetObj.wmin = wmin; - assetObj.hmin = hmin; - } - if (sizes && sizes.length) { - sizes = [].concat(...sizes); - assetObj.w = sizes[0]; - assetObj.h = sizes[1]; - } - const asset = {required: required ? 1 : 0, id}; - asset[name] = assetObj; - assets.push(asset); - } - }); - } - return { - ver: '1.2', - request: { - assets: assets, - context: 1, - plcmttype: 1, - ver: '1.2' - } - } -} - function createVideoRequest(bid) { const video = bid.mediaTypes.video; if (!video || !video.playerSize) return; diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 7c1db69b869..c5fd2189cb9 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -33,7 +33,6 @@ const GVLID = 1020; // const ENDPOINT_URL = '/api/bid?tn='; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const globals = {}; -const itemMaps = {}; /* ----- mguid:start ------ */ export const COOKIE_KEY_MGUID = '__mguid_'; @@ -139,7 +138,7 @@ function getItems(validBidRequests, bidderRequest) { const bidFloor = getBidFloor(req); const gpid = utils.deepAccess(req, 'ortb2Imp.ext.gpid') || - utils.deepAccess(req, 'params.placementId', 0); + utils.deepAccess(req, 'params.placementId', ''); const gdprConsent = {}; if (bidderRequest && bidderRequest.gdprConsent) { @@ -156,7 +155,8 @@ function getItems(validBidRequests, bidderRequest) { // if (mediaTypes.native) {} // banner广告类型 if (mediaTypes.banner) { - const id = '' + (i + 1); + // fix id is not unique where there are multiple requests in the same page + const id = getProperty(req, 'bidId') || ('' + (i + 1) + Math.random().toString(36).substring(2, 15)); ret = { id: id, bidfloor: bidFloor, @@ -177,10 +177,6 @@ function getItems(validBidRequests, bidderRequest) { }, tagid: req.params && req.params.tagid }; - itemMaps[id] = { - req, - ret - }; } return ret; @@ -217,7 +213,7 @@ function getParam(validBidRequests, bidderRequest) { const isMobile = getDevice() ? 1 : 0; // input test status by Publisher. more frequently for test true req const isTest = validBidRequests[0].params.test || 0; - const auctionId = getProperty(bidderRequest, 'auctionId'); + const bidderRequestId = getProperty(bidderRequest, 'bidderRequestId'); const items = getItems(validBidRequests, bidderRequest); const domain = utils.deepAccess(bidderRequest, 'refererInfo.domain') || document.domain; @@ -233,8 +229,7 @@ function getParam(validBidRequests, bidderRequest) { if (items && items.length) { const c = { - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - id: 'mgprebidjs_' + auctionId, + id: 'mgprebidjs_' + bidderRequestId, test: +isTest, at: 1, cur: ['USD'], @@ -295,7 +290,6 @@ function getParam(validBidRequests, bidderRequest) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - // aliases: ['ex'], // short code /** * Determines whether or not the given bid request is valid. * @@ -345,10 +339,9 @@ export const spec = { const bidResponses = []; for (const bid of bids) { const impid = getProperty(bid, 'impid'); - if (itemMaps[impid]) { - const bidId = getProperty(itemMaps[impid], 'req', 'bidId'); + if (impid) { const bidResponse = { - requestId: bidId, + requestId: getProperty(bid, 'impid'), cpm: getProperty(bid, 'price'), width: getProperty(bid, 'w'), height: getProperty(bid, 'h'), diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index cb358ec4687..1d0b8f61da0 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -139,6 +139,9 @@ export const spec = { bidResponse['mediasquare'][param] = value[param]; } }); + if ('burls' in value) { + bidResponse['mediasquare']['burls'] = value['burls']; + } if ('native' in value) { bidResponse['native'] = value['native']; bidResponse['mediaType'] = 'native'; @@ -182,9 +185,22 @@ export const spec = { } const params = { pbjs: '$prebid.version$', referer: encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation) }; const endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; - let paramsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment', 'ova']; + if (bid.hasOwnProperty('mediasquare')) { - paramsToSearchFor.forEach(param => { + // if burls then fire tracking pixels and exit + if (bid.mediasquare.hasOwnProperty('burls') && Array.isArray(bid.mediasquare.burls) && bid.mediasquare.burls.length > 0) { + bid.mediasquare.burls.forEach(burl => { + const url = burl && burl.url; + if (!url) return; + const method = (burl.method ?? "GET").toUpperCase(); + const data = (method === "POST" && burl.data ? burl.data : null); + ajax(url, null, data ? JSON.stringify(data) : null, {method: method, withCredentials: true}); + }); + return true; + } + // no burl so checking for other mediasquare params + let msqParamsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment', 'ova']; + msqParamsToSearchFor.forEach(param => { if (bid['mediasquare'].hasOwnProperty(param)) { params[param] = bid['mediasquare'][param]; if (typeof params[param] === 'number') { @@ -193,7 +209,8 @@ export const spec = { } }); }; - paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId', 'originalCpm', 'originalCurrency']; + + let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId', 'originalCpm', 'originalCurrency']; paramsToSearchFor.forEach(param => { if (bid.hasOwnProperty(param)) { params[param] = bid[param]; diff --git a/modules/mileBidAdapter.md b/modules/mileBidAdapter.md new file mode 100644 index 00000000000..0124c5f4327 --- /dev/null +++ b/modules/mileBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: Mile Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@mile.tech +``` + +# Description + +This bidder adapter connects to Mile demand sources. + +# Bid Params + +| Name | Scope | Description | Example | Type | +|---------------|----------|-----------------------------------|------------------|--------| +| `placementId` | required | The placement ID for the ad unit | `'12345'` | string | +| `siteId` | required | The site ID for the publisher | `'site123'` | string | +| `publisherId` | required | The publisher ID | `'pub456'` | string | + +# Example Configuration + +```javascript +var adUnits = [{ + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'mile', + params: { + placementId: 'test-placement', + siteId: 'test-site', + publisherId: 'test-pub' + } + }] +}]; +``` + +# User Sync Configuration + +To enable user syncing, configure Prebid.js with: + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, // Enable iframe syncs + filterSettings: { + iframe: { + bidders: ['mile'], + filter: 'include' + } + } + } +}); +``` + +# Test Parameters + +```javascript +{ + bidder: 'mile', + params: { + placementId: 'test-placement', + siteId: 'test-site', + publisherId: 'test-publisher-id' + } +} +``` + diff --git a/modules/mileBidAdapter.ts b/modules/mileBidAdapter.ts new file mode 100644 index 00000000000..f24feb8b74b --- /dev/null +++ b/modules/mileBidAdapter.ts @@ -0,0 +1,428 @@ +import { type BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, deepSetValue, generateUUID, logInfo, logError } from '../src/utils.js'; +import { getDNT } from '../libraries/dnt/index.js'; +import { ajax } from '../src/ajax.js'; + +/** + * Mile Bid Adapter + * + * This adapter handles: + * 1. User syncs by sending requests to the prebid server cookie sync endpoint + * 2. Bid requests by parsing necessary parameters from the prebid auction + */ + +const BIDDER_CODE = 'mile'; + +const MILE_BIDDER_HOST = 'https://pbs.atmtd.com'; +const ENDPOINT_URL = `${MILE_BIDDER_HOST}/mile/v1/request`; +const USER_SYNC_ENDPOINT = `https://scripts.atmtd.com/user-sync/load-cookie.html`; + +const MILE_ANALYTICS_ENDPOINT = `https://e01.atmtd.com/bidanalytics-event/json`; + +type MileBidParams = { + placementId: string; + siteId: string; + publisherId: string; +}; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: MileBidParams; + } +} + +export let siteIdTracker : string | undefined; +export let publisherIdTracker : string | undefined; + +export function getLowestFloorPrice(bid) { + let floorPrice: number; + + if (typeof bid.getFloor === 'function') { + let sizes = [] + // Get floor prices for each banner size in the bid request + if (deepAccess(bid, 'mediaTypes.banner.sizes')) { + sizes = deepAccess(bid, 'mediaTypes.banner.sizes') + } else if (bid.sizes) { + sizes = bid.sizes + } + + sizes.forEach((size: string | number[]) => { + const [w, h] = typeof size === 'string' ? size.split('x') : size as number[]; + const floor = bid.getFloor({ currency: 'USD', mediaType: '*', size: [Number(w), Number(h)] }); + if (floor && floor.floor) { + if (floorPrice === undefined) { + floorPrice = floor.floor; + } else { + floorPrice = Math.min(floorPrice, floor.floor); + } + } + }); + } else { + floorPrice = 0 + } + + return floorPrice +} + +export const spec: BidderSpec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + const params = bid.params; + + if (!params?.placementId) { + logError(`${BIDDER_CODE}: Missing required param: placementId`); + return false; + } + + if (!params?.siteId) { + logError(`${BIDDER_CODE}: Missing required param: siteId`); + return false; + } + + if (!params?.publisherId) { + logError(`${BIDDER_CODE}: Missing required param: publisherId`); + return false; + } + + if (siteIdTracker === undefined) { + siteIdTracker = params.siteId; + } else if (siteIdTracker !== params.siteId) { + logError(`${BIDDER_CODE}: Site ID mismatch: ${siteIdTracker} !== ${params.siteId}`); + return false; + } + + if (publisherIdTracker === undefined) { + publisherIdTracker = params.publisherId; + } else if (publisherIdTracker !== params.publisherId) { + logError(`${BIDDER_CODE}: Publisher ID mismatch: ${publisherIdTracker} !== ${params.publisherId}`); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * Builds an OpenRTB 2.5 compliant bid request. + * + * @param validBidRequests - An array of valid bids + * @param bidderRequest - The master bidder request object + * @returns ServerRequest info describing the request to the server + */ + buildRequests: function (validBidRequests, bidderRequest) { + logInfo(`${BIDDER_CODE}: Building batched request for ${validBidRequests.length} bids`); + + // Build imp[] array - one impression object per bid request + const imps = validBidRequests.map((bid) => { + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') || []; + const floorPrice = getLowestFloorPrice(bid); + + const imp: any = { + id: bid.bidId, + tagid: bid.params.placementId, + secure: 1, + banner: { + format: sizes.map((size: number[]) => ({ + w: size[0], + h: size[1], + })) + }, + ext: { + adUnitCode: bid.adUnitCode, + placementId: bid.params.placementId, + gpid: deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), + }, + }; + + // Add bidfloor if available + if (floorPrice > 0) { + imp.bidfloor = floorPrice; + imp.bidfloorcur = 'USD'; + } + + return imp; + }); + + // Build the OpenRTB 2.5 BidRequest object + const openRtbRequest: any = { + id: bidderRequest.bidderRequestId || generateUUID(), + imp: imps, + tmax: bidderRequest.timeout, + cur: ['USD'], + site: { + id: siteIdTracker, + page: deepAccess(bidderRequest, 'refererInfo.page') || '', + domain: deepAccess(bidderRequest, 'refererInfo.domain') || '', + ref: deepAccess(bidderRequest, 'refererInfo.ref') || '', + publisher: { + id: publisherIdTracker, + }, + }, + user: {}, + // Device object + device: { + ua: navigator.userAgent, + language: navigator.language?.split('-')[0] || 'en', + dnt: getDNT() ? 1 : 0, + w: window.screen?.width, + h: window.screen?.height, + }, + + // Source object with supply chain + source: { + tid: deepAccess(bidderRequest, 'ortb2.source.tid') + }, + }; + + // Add schain if available + const schain = deepAccess(validBidRequests, '0.ortb2.source.ext.schain'); + if (schain) { + deepSetValue(openRtbRequest, 'source.ext.schain', schain); + } + + // User object + const eids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (eids && eids.length) { + deepSetValue(openRtbRequest, 'user.ext.eids', eids); + } + + // Regs object for privacy/consent + const regs: any = { ext: {} }; + + // GDPR + if (bidderRequest.gdprConsent) { + regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + deepSetValue(openRtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString || ''); + } + + // US Privacy (CCPA) + if (bidderRequest.uspConsent) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // GPP + if (bidderRequest.gppConsent) { + regs.gpp = bidderRequest.gppConsent.gppString || ''; + regs.gpp_sid = bidderRequest.gppConsent.applicableSections || []; + } + + if (Object.keys(regs.ext).length > 0 || regs.gpp) { + openRtbRequest.regs = regs; + } + + // Merge any first-party data from ortb2 + if (bidderRequest.ortb2) { + if (bidderRequest.ortb2.site) { + openRtbRequest.site = { ...openRtbRequest.site, ...bidderRequest.ortb2.site }; + // Preserve publisher ID + openRtbRequest.site.publisher = { id: publisherIdTracker, ...bidderRequest.ortb2.site.publisher }; + } + if (bidderRequest.ortb2.user) { + openRtbRequest.user = { ...openRtbRequest.user, ...bidderRequest.ortb2.user }; + } + if (bidderRequest.ortb2.device) { + openRtbRequest.device = { ...openRtbRequest.device, ...bidderRequest.ortb2.device }; + } + } + + // Add prebid adapter version info + deepSetValue(openRtbRequest, 'ext.prebid.channel', { + name: 'pbjs', + version: '$prebid.version$', + }); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: openRtbRequest + }; + }, + + /** + * Unpack the OpenRTB 2.5 response from the server into a list of bids. + * + * @param serverResponse - A successful response from the server. + * @param request - The request that was sent to the server. + * @returns An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + const bidResponses: any[] = []; + + if (!serverResponse?.body) { + logInfo(`${BIDDER_CODE}: Empty server response`); + return bidResponses; + } + + const response = serverResponse.body; + const currency = response.cur || 'USD'; + + // OpenRTB 2.5 response format: seatbid[] contains bid[] + const seatbids = response.bids || []; + + seatbids.forEach((bid: any) => { + if (!bid || !bid.cpm) { + return; + } + + const bidResponse = { + requestId: bid.requestId, + cpm: parseFloat(bid.cpm), + width: parseInt(bid.width || bid.w, 10), + height: parseInt(bid.height || bid.h, 10), + creativeId: bid.creativeId || bid.crid || bid.id, + currency: currency, + netRevenue: true, + bidder: BIDDER_CODE, + ttl: bid.ttl || 300, + ad: bid.ad, + mediaType: BANNER, + meta: { + advertiserDomains: bid.adomain || [], + upstreamBidder: bid.upstreamBidder || '', + siteUID: deepAccess(response, 'site.id') || '', + publisherID: deepAccess(response, 'site.publisher.id') || '', + page: deepAccess(response, 'site.page') || '', + domain: deepAccess(response, 'site.domain') || '', + } + }; + + // Handle nurl (win notice URL) if present + if (bid.nurl) { + (bidResponse as any).nurl = bid.nurl; + } + + // Handle burl (billing URL) if present + if (bid.burl) { + (bidResponse as any).burl = bid.burl; + } + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + /** + * Register user sync pixels or iframes to be dropped after the auction. + * + * @param syncOptions - Which sync types are allowed (iframe, image/pixel) + * @param serverResponses - Array of server responses from the auction + * @param gdprConsent - GDPR consent data + * @param uspConsent - US Privacy consent string + * @param gppConsent - GPP consent data + * @returns Array of user sync objects to be executed + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent, + gppConsent + ) { + logInfo(`${BIDDER_CODE}: getUserSyncs called`, { + iframeEnabled: syncOptions.iframeEnabled + }); + + const syncs = []; + + // Build query parameters for consent + const queryParams: string[] = []; + + // GDPR consent + if (gdprConsent) { + queryParams.push(`gdpr=${gdprConsent.gdprApplies ? 1 : 0}`); + queryParams.push(`gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`); + } + + // US Privacy / CCPA + if (uspConsent) { + queryParams.push(`us_privacy=${encodeURIComponent(uspConsent)}`); + } + + // GPP consent + if (gppConsent?.gppString) { + queryParams.push(`gpp=${encodeURIComponent(gppConsent.gppString)}`); + if (gppConsent.applicableSections?.length) { + queryParams.push(`gpp_sid=${encodeURIComponent(gppConsent.applicableSections.join(','))}`); + } + } + + const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : ''; + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe' as const, + url: `${USER_SYNC_ENDPOINT}${queryString}`, + }); + } + + return syncs; + }, + + /** + * Called when a bid from this adapter wins the auction. + * Sends an XHR POST request to the bid's nurl (win notification URL). + * + * @param bid - The winning bid object + */ + onBidWon: function (bid) { + logInfo(`${BIDDER_CODE}: Bid won`, bid); + + const winNotificationData = { + adUnitCode: bid.adUnitCode, + metaData: { + impressionID: [bid.requestId], + }, + ua: navigator.userAgent, + timestamp: Date.now(), + winningSize: `${bid.width}x${bid.height}`, + cpm: bid.cpm, + eventType: 'mile-bidder-win-notify', + winningBidder: deepAccess(bid, 'meta.upstreamBidder') || '', + siteUID: deepAccess(bid, 'meta.siteUID') || '', + yetiPublisherID: deepAccess(bid, 'meta.publisherID') || '', + page: deepAccess(bid, 'meta.page') || '', + site: deepAccess(bid, 'meta.domain') || '', + } + + ajax(MILE_ANALYTICS_ENDPOINT, null, JSON.stringify([winNotificationData]), { method: 'POST'}); + + // @ts-expect-error - bid.nurl is not defined + if (bid.nurl) ajax(bid.nurl, null, null, { method: 'GET' }); + }, + + /** + * Called when bid requests timeout. + * Sends analytics notification for timed out bids. + * + * @param timeoutData - Array of bid requests that timed out + */ + onTimeout: function (timeoutData) { + logInfo(`${BIDDER_CODE}: Timeout for ${timeoutData.length} bid(s)`, timeoutData); + + if (timeoutData.length === 0) return; + + const timedOutBids = []; + + timeoutData.forEach((bid) => { + const timeoutNotificationData = { + adUnitCode: bid.adUnitCode, + metaData: { + impressionID: [bid.bidId], + configuredTimeout: [bid.timeout.toString()], + }, + ua: navigator.userAgent, + timestamp: Date.now(), + eventType: 'mile-bidder-timeout' + }; + + timedOutBids.push(timeoutNotificationData); + }); + + ajax(MILE_ANALYTICS_ENDPOINT, null, JSON.stringify(timedOutBids), { method: 'POST'}); + }, +}; + +registerBidder(spec); diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 573f20fb302..dba7a723693 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -157,6 +157,7 @@ export const spec = { serverResponses, gdprConsent = {}, uspConsent, + gppConsent, ) { if (!syncOptions.iframeEnabled || !this.msnaApiKey) { return []; @@ -172,6 +173,13 @@ export const spec = { if (uspConsent) { url.searchParams.append('us_privacy', uspConsent); } + if (gppConsent?.gppString) { + url.searchParams.append('gpp', gppConsent.gppString); + url.searchParams.append( + 'gpp_sid', + (gppConsent.applicableSections || []).join(','), + ); + } return [{ type: 'iframe', url: url.href }]; }, diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md index ccfc43e3aab..dee9cc12773 100644 --- a/modules/mobianRtdProvider.md +++ b/modules/mobianRtdProvider.md @@ -160,6 +160,36 @@ p1 = Advertisers (via Campaign IDs) should target these personas *AP Values is in the early stages of testing and is subject to change. +------------------ + +Additional Results Fields (API response) + +The fields below are present in the Mobian Contextual API `results` schema and are useful for downstream interpretation of content maturity and taxonomy. + +mobianMpaaRating: + +Type: integer | null + +Description: MPAA-style maturity rating score represented as an integer value in the API response. + +Behavior when unavailable: omitted when null. + +mobianEsrbRating: + +Type: integer | null + +Description: ESRB-style maturity rating score represented as an integer value in the API response. + +Behavior when unavailable: omitted when null. + +mobianContentTaxonomy: + +Type: string[] + +Description: IAB content taxonomy categories (broad topic buckets such as "News" or "Health"). + +Behavior when unavailable: may be returned as an empty array. + ## GAM Targeting: On each page load, the Mobian RTD module finds each ad slot on the page and performs the following function: diff --git a/modules/mycodemediaBidAdapter.js b/modules/mycodemediaBidAdapter.js new file mode 100644 index 00000000000..592a659f3f1 --- /dev/null +++ b/modules/mycodemediaBidAdapter.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 = 'mycodemedia'; +const AD_URL = 'https://east-backend.mycodemedia.com/pbjs'; +const SYNC_URL = 'https://usersync.mycodemedia.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/mycodemediaBidAdapter.md b/modules/mycodemediaBidAdapter.md new file mode 100644 index 00000000000..484233dd72d --- /dev/null +++ b/modules/mycodemediaBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: MyCodeMedia Bidder Adapter +Module Type: MyCodeMedia Bidder Adapter +Maintainer: support-platform@mycodemedia.com +``` + +# Description + +Connects to MyCodeMedia exchange for bids. +MyCodeMedia 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: 'mycodemedia', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'mycodemedia', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'mycodemedia', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 3255547aa47..8371c20fb88 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -1,5 +1,6 @@ /** * @module neuwoRtdProvider + * @version 2.2.6 * @author Grzegorz Malisz * @see {project-root-directory}/integrationExamples/gpt/neuwoRtdProvider_example.html for an example/testing page. * @see {project-root-directory}/test/spec/modules/neuwoRtdProvider_spec.js for unit tests. @@ -7,8 +8,10 @@ * This module is a Prebid.js Real-Time Data (RTD) provider that integrates with the Neuwo API. * * It fetches contextual marketing categories (IAB content and audience) for the current page from the Neuwo API. - * The retrieved data is then injected into the bid request as OpenRTB (ORTB2)`site.content.data` + * The retrieved data is then injected into the bid request as OpenRTB (ORTB2) `site.content.data` * and `user.data` fragments, making it available for bidders to use in their decisioning process. + * Additionally, when enabled, the module populates OpenRTB 2.5 category fields (`ortb2.site.cat`, + * `ortb2.site.sectioncat`, `ortb2.site.pagecat`, `ortb2.site.content.cat`) with IAB Content Taxonomy 1.0 segments. * * @see {@link https://docs.prebid.org/dev-docs/add-rtd-submodule.html} for more information on development of Prebid.js RTD modules. * @see {@link https://docs.prebid.org/features/firstPartyData.html} for more information on Prebid.js First Party Data. @@ -18,24 +21,40 @@ import { ajax } from "../src/ajax.js"; import { submodule } from "../src/hook.js"; import { getRefererInfo } from "../src/refererDetection.js"; -import { deepSetValue, logError, logInfo, mergeDeep } from "../src/utils.js"; +import { + deepSetValue, + logError, + logInfo, + logWarn, + mergeDeep, +} from "../src/utils.js"; const MODULE_NAME = "NeuwoRTDModule"; +const MODULE_VERSION = "2.2.6"; export const DATA_PROVIDER = "www.neuwo.ai"; -// Cached API response to avoid redundant requests. -let globalCachedResponse; +// Default IAB Content Taxonomy version +const DEFAULT_IAB_CONTENT_TAXONOMY_VERSION = "2.2"; + +// Maximum number of cached API responses to keep. Oldest entries are evicted when exceeded. +const MAX_CACHE_ENTRIES = 10; +// Cached API responses keyed by full API URL to avoid redundant requests. +let cachedResponses = {}; +// In-flight request promises keyed by full API URL to prevent duplicate API calls during the same request cycle. +let pendingRequests = {}; /** - * Clears the cached API response. Primarily used for testing. + * Clears the cached API responses and pending requests. Primarily used for testing. * @private */ export function clearCache() { - globalCachedResponse = undefined; + cachedResponses = {}; + pendingRequests = {}; } // Maps the IAB Content Taxonomy version string to the corresponding segtax ID. // Based on https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/main/AdCOM%20v1.0%20FINAL.md#list--category-taxonomies- +// prettier-ignore const IAB_CONTENT_TAXONOMY_MAP = { "1.0": 1, "2.0": 2, @@ -53,14 +72,14 @@ const IAB_CONTENT_TAXONOMY_MAP = { * @returns {boolean} `true` if the module is configured correctly, otherwise `false`. */ function init(config, userConsent) { - logInfo(MODULE_NAME, "init:", config, userConsent); + logInfo(MODULE_NAME, "init():", "Version " + MODULE_VERSION, config, userConsent); const params = config?.params || {}; if (!params.neuwoApiUrl) { - logError(MODULE_NAME, "init:", "Missing Neuwo Edge API Endpoint URL"); + logError(MODULE_NAME, "init():", "Missing Neuwo Edge API Endpoint URL"); return false; } if (!params.neuwoApiToken) { - logError(MODULE_NAME, "init:", "Missing Neuwo API Token missing"); + logError(MODULE_NAME, "init():", "Missing Neuwo API Token"); return false; } return true; @@ -69,6 +88,9 @@ function init(config, userConsent) { /** * Fetches contextual data from the Neuwo API and enriches the bid request object with IAB categories. * Uses cached response if available to avoid redundant API calls. + * Automatically detects API capabilities from the endpoint URL format: + * - URLs containing "/v1/iab" use GET requests with server-side filtering + * - Other URLs use GET requests with client-side filtering (legacy support) * * @param {Object} reqBidsConfigObj The bid request configuration object. * @param {function} callback The callback function to continue the auction. @@ -77,70 +99,272 @@ function init(config, userConsent) { * @param {string} config.params.neuwoApiUrl The Neuwo API endpoint URL. * @param {string} config.params.neuwoApiToken The Neuwo API authentication token. * @param {string} [config.params.websiteToAnalyseUrl] Optional URL to analyze instead of current page. - * @param {string} [config.params.iabContentTaxonomyVersion] IAB content taxonomy version (default: "3.0"). - * @param {boolean} [config.params.enableCache=true] If true, caches API responses to avoid redundant requests (default: true). + * @param {string} [config.params.iabContentTaxonomyVersion="2.2"] IAB Content Taxonomy version. + * @param {boolean} [config.params.enableCache=true] If true, caches API responses to avoid redundant requests. + * @param {boolean} [config.params.enableOrtb25Fields=true] If true, populates OpenRTB 2.5 category fields (site.cat, site.sectioncat, site.pagecat, site.content.cat) with IAB Content Taxonomy 1.0 segments. * @param {boolean} [config.params.stripAllQueryParams] If true, strips all query parameters from the URL. * @param {string[]} [config.params.stripQueryParamsForDomains] List of domains for which to strip all query params. * @param {string[]} [config.params.stripQueryParams] List of specific query parameter names to strip. * @param {boolean} [config.params.stripFragments] If true, strips URL fragments (hash). + * @param {Object} [config.params.iabTaxonomyFilters] Per-tier filtering configuration for IAB Taxonomies. * @param {Object} userConsent The user consent object. */ -export function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - logInfo(MODULE_NAME, "getBidRequestData:", "starting getBidRequestData", config); +export function getBidRequestData( + reqBidsConfigObj, + callback, + config, + userConsent +) { + logInfo( + MODULE_NAME, + "getBidRequestData():", + "starting getBidRequestData", + config + ); const { websiteToAnalyseUrl, neuwoApiUrl, neuwoApiToken, - iabContentTaxonomyVersion, + iabContentTaxonomyVersion = DEFAULT_IAB_CONTENT_TAXONOMY_VERSION, enableCache = true, + enableOrtb25Fields = true, stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, stripFragments, + iabTaxonomyFilters, } = config.params; const rawUrl = websiteToAnalyseUrl || getRefererInfo().page; + if (!rawUrl) { + logError(MODULE_NAME, "getBidRequestData():", "No URL available to analyse"); + callback(); + return; + } const processedUrl = cleanUrl(rawUrl, { stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, - stripFragments + stripFragments, }); const pageUrl = encodeURIComponent(processedUrl); - // Adjusted for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') - const joiner = neuwoApiUrl.indexOf("?") < 0 ? "?" : "&"; - const neuwoApiUrlFull = - neuwoApiUrl + joiner + ["token=" + neuwoApiToken, "url=" + pageUrl].join("&"); + const contentSegtax = + IAB_CONTENT_TAXONOMY_MAP[iabContentTaxonomyVersion] || + IAB_CONTENT_TAXONOMY_MAP[DEFAULT_IAB_CONTENT_TAXONOMY_VERSION]; - const success = (response) => { - logInfo(MODULE_NAME, "getBidRequestData:", "Neuwo API raw response:", response); - try { - const responseParsed = JSON.parse(response); + // Detect whether the endpoint supports multi-taxonomy responses and server-side filtering. + // Use URL pathname to avoid false positives when "/v1/iab" appears in query params. + let isIabEndpoint = false; + try { + isIabEndpoint = new URL(neuwoApiUrl).pathname.includes("/v1/iab"); + } catch (e) { + isIabEndpoint = neuwoApiUrl.split("?")[0].includes("/v1/iab"); + } - if (enableCache) { - globalCachedResponse = responseParsed; - } + // Warn if OpenRTB 2.5 feature enabled with legacy endpoint + if (enableOrtb25Fields && !isIabEndpoint) { + logWarn( + MODULE_NAME, + "getBidRequestData():", + "OpenRTB 2.5 category fields require the /v1/iab endpoint" + ); + } - injectIabCategories(responseParsed, reqBidsConfigObj, iabContentTaxonomyVersion); - } catch (ex) { - logError(MODULE_NAME, "getBidRequestData:", "Error while processing Neuwo API response", ex); + const joiner = neuwoApiUrl.indexOf("?") < 0 ? "?" : "&"; + const urlParams = [ + "token=" + neuwoApiToken, + "url=" + pageUrl, + "_neuwo_prod=PrebidModule", + ]; + + // Request both IAB Content Taxonomy (based on config) and IAB Audience Taxonomy (segtax 4) + if (isIabEndpoint) { + urlParams.push("iabVersions=" + contentSegtax); + urlParams.push("iabVersions=4"); // IAB Audience 1.1 + + // Request IAB 1.0 for OpenRTB 2.5 fields if feature enabled. + // Skip when contentSegtax is already 1 -- already requested above. + if (enableOrtb25Fields && contentSegtax !== 1) { + urlParams.push("iabVersions=1"); // IAB Content 1.0 } - callback(); - }; - const error = (err) => { - logError(MODULE_NAME, "getBidRequestData:", "AJAX error:", err); - callback(); - }; + // Add flattened filter parameters to URL for GET request + const filterParams = buildFilterQueryParams( + iabTaxonomyFilters, + contentSegtax, + enableOrtb25Fields + ); + if (filterParams.length > 0) { + urlParams.push(...filterParams); + } + } - if (enableCache && globalCachedResponse) { - logInfo(MODULE_NAME, "getBidRequestData:", "Using cached response:", globalCachedResponse); - injectIabCategories(globalCachedResponse, reqBidsConfigObj, iabContentTaxonomyVersion); + const neuwoApiUrlFull = neuwoApiUrl + joiner + urlParams.join("&"); + + // For /v1/iab endpoints the full URL already encodes all config (iabVersions, filters). + // For legacy endpoints the URL only carries token + page URL, so append config-dependent + // values to the cache key to prevent different configs sharing a response that was + // transformed/filtered for a different taxonomy version or filter set. + let cacheKey = neuwoApiUrlFull; + if (!isIabEndpoint) { + cacheKey += "&_segtax=" + contentSegtax; + if (iabTaxonomyFilters && Object.keys(iabTaxonomyFilters).length > 0) { + cacheKey += "&_filters=" + JSON.stringify(iabTaxonomyFilters); + } + } + + // Cache flow: cached response -> pending request -> new request + // Each caller gets their own callback invoked when data is ready. + // Keyed by cacheKey to ensure different parameters never share cached data. + if (enableCache && cachedResponses[cacheKey]) { + // Previous request succeeded - use cached response immediately + logInfo( + MODULE_NAME, + "getBidRequestData():", + "Cache System:", + "Using cached response for:", + cacheKey + ); + injectIabCategories( + cachedResponses[cacheKey], + reqBidsConfigObj, + iabContentTaxonomyVersion, + enableOrtb25Fields + ); callback(); + } else if (enableCache && pendingRequests[cacheKey]) { + // Another caller started a request with the same params - wait for it + logInfo( + MODULE_NAME, + "getBidRequestData():", + "Cache System:", + "Waiting for pending request for:", + cacheKey + ); + pendingRequests[cacheKey] + .then((responseParsed) => { + if (responseParsed) { + injectIabCategories( + responseParsed, + reqBidsConfigObj, + iabContentTaxonomyVersion, + enableOrtb25Fields + ); + } + }) + .finally(() => callback()); } else { - logInfo(MODULE_NAME, "getBidRequestData:", "Calling Neuwo API Endpoint: ", neuwoApiUrlFull); - ajax(neuwoApiUrlFull, { success, error }, null); + // First request or cache disabled - make the API call + logInfo( + MODULE_NAME, + "getBidRequestData():", + "Cache System:", + "Calling Neuwo API Endpoint:", + neuwoApiUrlFull + ); + + const requestPromise = new Promise((resolve) => { + ajax( + neuwoApiUrlFull, + { + success: (response) => { + logInfo( + MODULE_NAME, + "getBidRequestData():", + "success():", + "Neuwo API raw response:", + response + ); + + let responseParsed; + try { + responseParsed = JSON.parse(response); + } catch (ex) { + logError( + MODULE_NAME, + "getBidRequestData():", + "success():", + "Error parsing Neuwo API response JSON:", + ex + ); + resolve(null); + return; + } + + try { + if (!isIabEndpoint) { + // Apply per-tier filtering to V1 format + const filteredMarketingCategories = filterIabTaxonomies( + responseParsed.marketing_categories, + iabTaxonomyFilters + ); + + // Transform filtered V1 response to unified internal format + responseParsed = transformV1ResponseToV2( + { marketing_categories: filteredMarketingCategories }, + contentSegtax + ); + } + + // Cache response, evicting oldest entry if at capacity. + // Only cache valid responses so failed requests can be retried. + if ( + enableCache && + responseParsed && + typeof responseParsed === "object" + ) { + // Object.keys() preserves string insertion order in modern JS engines. + const keys = Object.keys(cachedResponses); + if (keys.length >= MAX_CACHE_ENTRIES) { + delete cachedResponses[keys[0]]; + } + cachedResponses[cacheKey] = responseParsed; + } + + injectIabCategories( + responseParsed, + reqBidsConfigObj, + iabContentTaxonomyVersion, + enableOrtb25Fields + ); + resolve(responseParsed); + } catch (ex) { + logError( + MODULE_NAME, + "getBidRequestData():", + "success():", + "Error processing Neuwo API response:", + ex + ); + resolve(null); + } + }, + error: (err) => { + logError( + MODULE_NAME, + "getBidRequestData():", + "error():", + "AJAX error:", + err + ); + resolve(null); + }, + } + ); + }); + + if (enableCache) { + // Store promise so concurrent callers with same params can wait on it + pendingRequests[cacheKey] = requestPromise; + // Clear after settling so failed requests can be retried + requestPromise.finally(() => { + delete pendingRequests[cacheKey]; + }); + } + + // Signal this caller's auction to proceed once request completes + requestPromise.finally(() => callback()); } } @@ -160,14 +384,23 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen * @returns {string} The cleaned URL. */ export function cleanUrl(url, options = {}) { - const { stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, stripFragments } = options; + const { + stripAllQueryParams, + stripQueryParamsForDomains, + stripQueryParams, + stripFragments, + } = options; if (!url) { - logInfo(MODULE_NAME, "cleanUrl:", "Empty or null URL provided, returning as-is"); + logInfo( + MODULE_NAME, + "cleanUrl():", + "Empty or null URL provided, returning as-is" + ); return url; } - logInfo(MODULE_NAME, "cleanUrl:", "Input URL:", url, "Options:", options); + logInfo(MODULE_NAME, "cleanUrl():", "Input URL:", url, "Options:", options); try { const urlObj = new URL(url); @@ -175,21 +408,24 @@ export function cleanUrl(url, options = {}) { // Strip fragments if requested if (stripFragments === true) { urlObj.hash = ""; - logInfo(MODULE_NAME, "cleanUrl:", "Stripped fragment from URL"); + logInfo(MODULE_NAME, "cleanUrl():", "Stripped fragment from URL"); } // Option 1: Strip all query params unconditionally if (stripAllQueryParams === true) { urlObj.search = ""; const cleanedUrl = urlObj.toString(); - logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", cleanedUrl); + logInfo(MODULE_NAME, "cleanUrl():", "Output URL:", cleanedUrl); return cleanedUrl; } // Option 2: Strip all query params for specific domains - if (Array.isArray(stripQueryParamsForDomains) && stripQueryParamsForDomains.length > 0) { + if ( + Array.isArray(stripQueryParamsForDomains) && + stripQueryParamsForDomains.length > 0 + ) { const hostname = urlObj.hostname; - const shouldStripForDomain = stripQueryParamsForDomains.some(domain => { + const shouldStripForDomain = stripQueryParamsForDomains.some((domain) => { // Support exact match or subdomain match return hostname === domain || hostname.endsWith("." + domain); }); @@ -197,7 +433,7 @@ export function cleanUrl(url, options = {}) { if (shouldStripForDomain) { urlObj.search = ""; const cleanedUrl = urlObj.toString(); - logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", cleanedUrl); + logInfo(MODULE_NAME, "cleanUrl():", "Output URL:", cleanedUrl); return cleanedUrl; } } @@ -208,21 +444,25 @@ export function cleanUrl(url, options = {}) { // - "??" is treated as query parameter with key "?" and value "" if (Array.isArray(stripQueryParams) && stripQueryParams.length > 0) { const queryParams = urlObj.searchParams; - logInfo(MODULE_NAME, "cleanUrl:", `Query parameters to strip: ${stripQueryParams}`); - stripQueryParams.forEach(param => { + logInfo( + MODULE_NAME, + "cleanUrl():", + `Query parameters to strip: ${stripQueryParams}` + ); + stripQueryParams.forEach((param) => { queryParams.delete(param); }); urlObj.search = queryParams.toString(); const cleanedUrl = urlObj.toString(); - logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", cleanedUrl); + logInfo(MODULE_NAME, "cleanUrl():", "Output URL:", cleanedUrl); return cleanedUrl; } const finalUrl = urlObj.toString(); - logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", finalUrl); + logInfo(MODULE_NAME, "cleanUrl():", "Output URL:", finalUrl); return finalUrl; } catch (e) { - logError(MODULE_NAME, "cleanUrl:", "Error cleaning URL:", e); + logError(MODULE_NAME, "cleanUrl():", "Error cleaning URL:", e); return url; } } @@ -241,72 +481,435 @@ export function injectOrtbData(reqBidsConfigObj, path, data) { } /** - * Builds an IAB category data object for use in OpenRTB. + * Extracts all segment IDs from tier data into a flat array. + * Used for populating OpenRTB 2.5 category fields and building IAB data objects. + * + * @param {Object} tierData The tier data keyed by tier numbers (e.g., {"1": [{id: "IAB12"}], "2": [...]}). + * @returns {Array} Flat array of segment IDs (e.g., ["IAB12", "IAB12-3", "IAB12-5"]). + */ +export function extractCategoryIds(tierData) { + const ids = []; + + // Handle null, undefined, non-object, or array tierData + if (!tierData || typeof tierData !== "object" || Array.isArray(tierData)) { + return ids; + } + + // Process ALL tier keys present in tierData + Object.keys(tierData).forEach((tierKey) => { + const segments = tierData[tierKey]; + if (Array.isArray(segments)) { + segments.forEach((item) => { + if (item?.id) { + ids.push(item.id); + } + }); + } + }); + + return ids; +} + +/** + * Builds an IAB category data object for OpenRTB injection. + * Dynamically processes all tiers present in the response data. * - * @param {Object} marketingCategories Marketing Categories returned by Neuwo API. - * @param {string[]} tiers The tier keys to extract from marketingCategories. - * @param {number} segtax The IAB taxonomy version Id. - * @returns {Object} The constructed data object. + * @param {Object} tierData The tier data keyed by tier numbers (e.g., {"1": [...], "2": [...], "3": [...]}). + * @param {number} segtax The IAB Taxonomy segtax ID. + * @returns {Object} The OpenRTB data object with name, segment array, and ext.segtax. */ -export function buildIabData(marketingCategories, tiers, segtax) { - const data = { +export function buildIabData(tierData, segtax) { + const ids = extractCategoryIds(tierData); + return { name: DATA_PROVIDER, - segment: [], + segment: ids.map((id) => ({ id })), ext: { segtax }, }; +} + +/** + * v1 API specific + * Filters and limits a single tier's taxonomies based on relevance score and count. + * Used for client-side filtering with legacy endpoints. + * + * @param {Array} iabTaxonomies Array of IAB Taxonomy Segments objects with ID, label, and relevance. + * @param {Object} filter Filter configuration with optional threshold and limit properties. + * @returns {Array} Filtered and limited array of taxonomies, sorted by relevance (highest first). + */ +export function filterIabTaxonomyTier(iabTaxonomies, filter = {}) { + if (!Array.isArray(iabTaxonomies)) { + return []; + } + if (iabTaxonomies.length === 0) { + return iabTaxonomies; + } - tiers.forEach((tier) => { - const tierData = marketingCategories?.[tier]; + const { threshold, limit } = filter; + const hasThreshold = typeof threshold === "number" && threshold > 0; + const hasLimit = typeof limit === "number" && limit >= 0; + + // No effective filter configured -- return original order unchanged + if (!hasThreshold && !hasLimit) { + return iabTaxonomies; + } + + let filtered = [...iabTaxonomies]; // Create copy to avoid mutating original + + // Filter by minimum relevance score + if (hasThreshold) { + filtered = filtered.filter((item) => { + const relevance = parseFloat(item?.relevance); + return !isNaN(relevance) && relevance >= threshold; + }); + } + + // Sort by relevance (highest first) so limit keeps the most relevant items + if (hasLimit) { + filtered = filtered.sort((a, b) => { + const relA = parseFloat(a?.relevance) || 0; + const relB = parseFloat(b?.relevance) || 0; + return relB - relA; // Descending order + }); + filtered = filtered.slice(0, limit); + } + + return filtered; +} + +/** + * Maps tier configuration keys to API response keys. + */ +const TIER_KEY_MAP = { + ContentTier1: "iab_tier_1", + ContentTier2: "iab_tier_2", + ContentTier3: "iab_tier_3", + AudienceTier3: "iab_audience_tier_3", + AudienceTier4: "iab_audience_tier_4", + AudienceTier5: "iab_audience_tier_5", +}; + +/** + * v1 API specific + * Applies per-tier filtering to IAB taxonomies (client-side filtering for legacy endpoints). + * Filters taxonomies by relevance score and limits the count per tier. + * + * @param {Object} marketingCategories Marketing categories from legacy API response. + * @param {Object} [tierFilters] Per-tier filter configuration with human-readable tier names (e.g., {ContentTier1: {limit: 3, threshold: 0.75}}). + * @returns {Object} Filtered marketing categories with the same structure as input. + */ +export function filterIabTaxonomies(marketingCategories, tierFilters = {}) { + if (!marketingCategories || typeof marketingCategories !== "object") { + return marketingCategories; + } + + // If no filters provided, return original data + if (!tierFilters || Object.keys(tierFilters).length === 0) { + logInfo( + MODULE_NAME, + "filterIabTaxonomies():", + "No filters provided, returning original data" + ); + return marketingCategories; + } + + const filtered = {}; + + // Iterate through all tiers in the API response + Object.keys(marketingCategories).forEach((apiTierKey) => { + const tierData = marketingCategories[apiTierKey]; + + // Find the corresponding config key for this API tier + const configTierKey = Object.keys(TIER_KEY_MAP).find( + (key) => TIER_KEY_MAP[key] === apiTierKey + ); + + // Get filter for this tier (if configured) + const filter = configTierKey ? tierFilters[configTierKey] : {}; + + // Apply filter if this tier has data if (Array.isArray(tierData)) { - tierData.forEach((item) => { - const ID = item?.ID; - const label = item?.label; + filtered[apiTierKey] = filterIabTaxonomyTier(tierData, filter); + } else { + // Preserve non-array data as-is + filtered[apiTierKey] = tierData; + } + }); + + logInfo( + MODULE_NAME, + "filterIabTaxonomies():", + "Filtering results:", + "Original:", + marketingCategories, + "Filtered:", + filtered + ); + + return filtered; +} + +/** + * v1 API specific + * Transforms legacy API response format to unified internal format. + * Converts marketing_categories structure to segtax-based structure for consistent processing. + * + * Legacy format: { marketing_categories: { iab_tier_1: [...], iab_audience_tier_3: [...] } } + * Unified format: { "6": { "1": [...], "2": [...] }, "4": { "3": [...], "4": [...] } } + * + * @param {Object} v1Response The legacy API response with marketing_categories structure. + * @param {number} contentSegtax The segtax ID for content taxonomies (determined by iabContentTaxonomyVersion). + * @returns {Object} Unified format response keyed by segtax and tier numbers. + */ +export function transformV1ResponseToV2(v1Response, contentSegtax) { + const marketingCategories = v1Response?.marketing_categories || {}; + const contentSegtaxStr = String(contentSegtax); + const result = {}; + + // Content tiers: keyed by segtax from config + result[contentSegtaxStr] = {}; + if (marketingCategories.iab_tier_1) { + result[contentSegtaxStr]["1"] = transformSegmentsV1ToV2( + marketingCategories.iab_tier_1 + ); + } + if (marketingCategories.iab_tier_2) { + result[contentSegtaxStr]["2"] = transformSegmentsV1ToV2( + marketingCategories.iab_tier_2 + ); + } + if (marketingCategories.iab_tier_3) { + result[contentSegtaxStr]["3"] = transformSegmentsV1ToV2( + marketingCategories.iab_tier_3 + ); + } - if (ID && label) { - data.segment.push({ id: ID, name: label }); + // Audience tiers: segtax 4 + result["4"] = {}; + if (marketingCategories.iab_audience_tier_3) { + result["4"]["3"] = transformSegmentsV1ToV2( + marketingCategories.iab_audience_tier_3 + ); + } + if (marketingCategories.iab_audience_tier_4) { + result["4"]["4"] = transformSegmentsV1ToV2( + marketingCategories.iab_audience_tier_4 + ); + } + if (marketingCategories.iab_audience_tier_5) { + result["4"]["5"] = transformSegmentsV1ToV2( + marketingCategories.iab_audience_tier_5 + ); + } + + return result; +} + +/** + * v1 API specific + * Transforms segment objects from legacy format to unified format. + * Maps field names from legacy API response to unified internal representation. + * + * Legacy format: { ID: "123", label: "Category Name", relevance: "0.95" } + * Unified format: { id: "123", name: "Category Name", relevance: "0.95" } + * + * @param {Array} segments Array of legacy segment objects with ID, label, relevance. + * @returns {Array} Array of unified format segment objects with id, name, relevance. + */ +export function transformSegmentsV1ToV2(segments) { + if (!Array.isArray(segments)) return []; + return segments.map((seg) => ({ + id: seg.ID, + name: seg.label, + relevance: seg.relevance, + })); +} + +/** + * Builds flattened query parameters from IAB taxonomy filters. + * Converts human-readable tier names directly to query parameter format for GET requests. + * + * @param {Object} iabTaxonomyFilters Publisher's tier filter configuration using human-readable tier names. + * @param {number} contentSegtax The segtax ID for content taxonomies (determined by iabContentTaxonomyVersion). + * @param {boolean} [enableOrtb25Fields=true] If true, also applies filters to IAB Content Taxonomy 1.0 (segtax 1) for OpenRTB 2.5 category fields. + * @returns {Array} Array of query parameter strings (e.g., ["filter_6_1_limit=3", "filter_6_1_threshold=0.5"]). + * + * @example + * Input: { ContentTier1: { limit: 3, threshold: 0.5 }, AudienceTier3: { limit: 2 } }, contentSegtax=6 + * Output: ["filter_6_1_limit=3", "filter_6_1_threshold=0.5", "filter_4_3_limit=2"] + */ +export function buildFilterQueryParams( + iabTaxonomyFilters, + contentSegtax, + enableOrtb25Fields = true +) { + const params = []; + + if (!iabTaxonomyFilters || typeof iabTaxonomyFilters !== "object") { + return params; + } + + const TIER_TO_SEGTAX = { + ContentTier1: { segtax: contentSegtax, tier: "1" }, + ContentTier2: { segtax: contentSegtax, tier: "2" }, + ContentTier3: { segtax: contentSegtax, tier: "3" }, + AudienceTier3: { segtax: 4, tier: "3" }, + AudienceTier4: { segtax: 4, tier: "4" }, + AudienceTier5: { segtax: 4, tier: "5" }, + }; + + // Build query params from tier mappings + Object.entries(iabTaxonomyFilters).forEach(([tierName, filter]) => { + const mapping = TIER_TO_SEGTAX[tierName]; + if (mapping && filter && typeof filter === "object") { + const segtax = mapping.segtax; + const tier = mapping.tier; + + // Add each filter property (limit, threshold) as a query parameter + Object.keys(filter).forEach((prop) => { + const value = filter[prop]; + if (value !== undefined && value !== null) { + params.push(`filter_${segtax}_${tier}_${prop}=${value}`); } }); } }); - return data; + // Apply same filters to IAB 1.0 (segtax 1) for OpenRTB 2.5 fields. + // Skip when contentSegtax is already 1 -- the first loop already emitted filter_1_* params. + // Note: IAB 1.0 only has tiers 1 and 2 (tier 3 will be ignored if configured) + if (enableOrtb25Fields && contentSegtax !== 1) { + if (iabTaxonomyFilters.ContentTier1) { + Object.keys(iabTaxonomyFilters.ContentTier1).forEach((prop) => { + const value = iabTaxonomyFilters.ContentTier1[prop]; + if (value !== undefined && value !== null) { + params.push(`filter_1_1_${prop}=${value}`); + } + }); + } + + if (iabTaxonomyFilters.ContentTier2) { + Object.keys(iabTaxonomyFilters.ContentTier2).forEach((prop) => { + const value = iabTaxonomyFilters.ContentTier2[prop]; + if (value !== undefined && value !== null) { + params.push(`filter_1_2_${prop}=${value}`); + } + }); + } + } + + return params; } /** - * Processes the Neuwo API response to build and inject IAB content and audience categories - * into the bid request object. + * Processes the Neuwo API response and injects IAB Content and Audience Segments into the bid request. + * Extracts Segments from the response and injects them into ORTB2 structure. + * + * Response format: { "6": { "1": [{id, name}], "2": [...] }, "4": { "3": [...], "4": [...] } } + * - Content taxonomies are injected into ortb2.site.content.data + * - Audience taxonomies are injected into ortb2.user.data + * - If enableOrtb25Fields is true, IAB 1.0 segments are injected into OpenRTB 2.5 category fields * - * @param {Object} responseParsed The parsed JSON response from the Neuwo API. - * @param {Object} reqBidsConfigObj The bid request configuration object to be modified. - * @param {string} iabContentTaxonomyVersion The version of the IAB content taxonomy to use for segtax mapping. + * Only injects data if segments exist to avoid adding empty data structures. + * + * @param {Object} responseParsed The parsed API response. + * @param {Object} reqBidsConfigObj The bid request configuration object to be enriched. + * @param {string} iabContentTaxonomyVersion The IAB Content Taxonomy version for segtax mapping. + * @param {boolean} [enableOrtb25Fields=true] If true, populates OpenRTB 2.5 category fields with IAB Content Taxonomy 1.0 segments. */ -function injectIabCategories(responseParsed, reqBidsConfigObj, iabContentTaxonomyVersion) { - const marketingCategories = responseParsed.marketing_categories; - - if (!marketingCategories) { - logError(MODULE_NAME, "injectIabCategories:", "No Marketing Categories in Neuwo API response."); - return +export function injectIabCategories( + responseParsed, + reqBidsConfigObj, + iabContentTaxonomyVersion, + enableOrtb25Fields = true +) { + if (!responseParsed || typeof responseParsed !== "object") { + logError(MODULE_NAME, "injectIabCategories():", "Invalid response format"); + return; } - // Process content categories - const contentTiers = ["iab_tier_1", "iab_tier_2", "iab_tier_3"]; - const contentData = buildIabData( - marketingCategories, - contentTiers, - IAB_CONTENT_TAXONOMY_MAP[iabContentTaxonomyVersion] || IAB_CONTENT_TAXONOMY_MAP["3.0"] - ); + const contentSegtax = + IAB_CONTENT_TAXONOMY_MAP[iabContentTaxonomyVersion] || + IAB_CONTENT_TAXONOMY_MAP[DEFAULT_IAB_CONTENT_TAXONOMY_VERSION]; + const contentSegtaxStr = String(contentSegtax); + + // Extract IAB Content Taxonomy data for the configured version + const contentTiers = responseParsed[contentSegtaxStr] || {}; + const contentData = buildIabData(contentTiers, contentSegtax); + + // Extract IAB Audience Taxonomy data + const audienceTiers = responseParsed["4"] || {}; + const audienceData = buildIabData(audienceTiers, 4); - // Process audience categories - const audienceTiers = ["iab_audience_tier_3", "iab_audience_tier_4", "iab_audience_tier_5"]; - const audienceData = buildIabData(marketingCategories, audienceTiers, 4); + logInfo( + MODULE_NAME, + "injectIabCategories():", + "contentData structure:", + contentData + ); + logInfo( + MODULE_NAME, + "injectIabCategories():", + "audienceData structure:", + audienceData + ); - logInfo(MODULE_NAME, "injectIabCategories:", "contentData structure:", contentData); - logInfo(MODULE_NAME, "injectIabCategories:", "audienceData structure:", audienceData); + // Inject content and audience data independently to avoid sending empty structures + if (contentData.segment.length > 0) { + injectOrtbData(reqBidsConfigObj, "site.content.data", [contentData]); + logInfo( + MODULE_NAME, + "injectIabCategories():", + "Injected content data into site.content.data" + ); + } else { + logInfo( + MODULE_NAME, + "injectIabCategories():", + "No content segments to inject, skipping site.content.data" + ); + } - injectOrtbData(reqBidsConfigObj, "site.content.data", [contentData]); - injectOrtbData(reqBidsConfigObj, "user.data", [audienceData]); + if (audienceData.segment.length > 0) { + injectOrtbData(reqBidsConfigObj, "user.data", [audienceData]); + logInfo( + MODULE_NAME, + "injectIabCategories():", + "Injected audience data into user.data" + ); + } else { + logInfo( + MODULE_NAME, + "injectIabCategories():", + "No audience segments to inject, skipping user.data" + ); + } - logInfo(MODULE_NAME, "injectIabCategories:", "post-injection bidsConfig", reqBidsConfigObj); + // Inject OpenRTB 2.5 category fields if feature enabled + if (enableOrtb25Fields) { + const iab10Tiers = responseParsed["1"] || {}; // Segtax 1 = IAB Content 1.0 + const categoryIds = extractCategoryIds(iab10Tiers); // ["IAB12", "IAB12-3", ...] + + if (categoryIds.length > 0) { + // Inject same array into all four OpenRTB 2.5 category fields + injectOrtbData(reqBidsConfigObj, "site.cat", categoryIds); + injectOrtbData(reqBidsConfigObj, "site.sectioncat", categoryIds); + injectOrtbData(reqBidsConfigObj, "site.pagecat", categoryIds); + injectOrtbData(reqBidsConfigObj, "site.content.cat", categoryIds); + + logInfo( + MODULE_NAME, + "injectIabCategories():", + "Injected OpenRTB 2.5 category fields:", + categoryIds + ); + } else { + logInfo( + MODULE_NAME, + "injectIabCategories():", + "No IAB 1.0 segments available for OpenRTB 2.5 fields" + ); + } + } } export const neuwoRtdModule = { diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md index 804130be1e6..e6c2798a1ad 100644 --- a/modules/neuwoRtdProvider.md +++ b/modules/neuwoRtdProvider.md @@ -10,55 +10,47 @@ The Neuwo RTD provider fetches real-time contextual data from the Neuwo API. Whe This data is then added to the bid request by populating the OpenRTB 2.x objects `ortb2.site.content.data` (for IAB Content Taxonomy) and `ortb2.user.data` (for IAB Audience Taxonomy). This enrichment allows bidders to leverage Neuwo's contextual analysis for more precise targeting and decision-making. +Additionally, when enabled, the module populates OpenRTB 2.5 category fields (`ortb2.site.cat`, `ortb2.site.sectioncat`, `ortb2.site.pagecat`, `ortb2.site.content.cat`) with IAB Content Taxonomy 1.0 segments. + Here is an example scheme of the data injected into the `ortb2` object by our module: ```javascript ortb2: { site: { + // OpenRTB 2.5 category fields (IAB Content Taxonomy 1.0) + cat: ["IAB12", "IAB12-3", "IAB12-5"], + sectioncat: ["IAB12", "IAB12-3", "IAB12-5"], + pagecat: ["IAB12", "IAB12-3", "IAB12-5"], content: { + // OpenRTB 2.5 category field (IAB Content Taxonomy 1.0) + cat: ["IAB12", "IAB12-3", "IAB12-5"], // IAB Content Taxonomy data is injected here data: [{ name: "www.neuwo.ai", - segment: [{ - id: "274", - name: "Home & Garden", - }, - { - id: "42", - name: "Books and Literature", - }, - { - id: "210", - name: "Food & Drink", - }, + segment: [ + { id: "274" }, + { id: "42" }, + { id: "210" }, ], ext: { - segtax: 7, + segtax: 6, }, - }, ], + }], }, }, user: { // IAB Audience Taxonomy data is injected here data: [{ name: "www.neuwo.ai", - segment: [{ - id: "49", - name: "Demographic | Gender | Female |", - }, - { - id: "161", - name: "Demographic | Marital Status | Married |", - }, - { - id: "6", - name: "Demographic | Age Range | 30-34 |", - }, + segment: [ + { id: "49" }, + { id: "161" }, + { id: "6" }, ], ext: { segtax: 4, }, - }, ], + }], }, } ``` @@ -74,16 +66,17 @@ This module is configured as part of the `realTimeData.dataProviders` object. ```javascript pbjs.setConfig({ realTimeData: { - auctionDelay: 500, // Value can be adjusted based on the needs + auctionDelay: 500, // Value can be adjusted based on the needs. Recommended to start with value `500` dataProviders: [ { name: "NeuwoRTDModule", - waitForIt: true, + waitForIt: true, // Recommended to be set to `true` params: { neuwoApiUrl: "", neuwoApiToken: "", - iabContentTaxonomyVersion: "3.0", + iabContentTaxonomyVersion: "2.2", enableCache: true, // Default: true. Caches API responses to avoid redundant requests + enableOrtb25Fields: true, // Default: true. }, }, ], @@ -93,23 +86,61 @@ pbjs.setConfig({ **Parameters** -| Name | Type | Required | Default | Description | -| :---------------------------------- | :------- | :------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | String | Yes | | The name of the module, which is `NeuwoRTDModule`. | -| `params` | Object | Yes | | Container for module-specific parameters. | -| `params.neuwoApiUrl` | String | Yes | | The endpoint URL for the Neuwo Edge API. | -| `params.neuwoApiToken` | String | Yes | | Your unique API token provided by Neuwo. | -| `params.iabContentTaxonomyVersion` | String | No | `'3.0'` | Specifies the version of the IAB Content Taxonomy to be used. Supported values: `'2.2'`, `'3.0'`. | -| `params.enableCache` | Boolean | No | `true` | If `true`, caches API responses to avoid redundant requests for the same page during the session. Set to `false` to disable caching and make a fresh API call on every bid request. | -| `params.stripAllQueryParams` | Boolean | No | `false` | If `true`, strips all query parameters from the URL before analysis. Takes precedence over other stripping options. | -| `params.stripQueryParamsForDomains` | String[] | No | `[]` | List of domains for which to strip **all** query parameters. When a domain matches, all query params are removed for that domain and all its subdomains (e.g., `'example.com'` strips params for both `'example.com'` and `'sub.example.com'`). This option takes precedence over `stripQueryParams` for matching domains. | -| `params.stripQueryParams` | String[] | No | `[]` | List of specific query parameter names to strip from the URL (e.g., `['utm_source', 'fbclid']`). Other parameters are preserved. Only applies when the domain does not match `stripQueryParamsForDomains`. | -| `params.stripFragments` | Boolean | No | `false` | If `true`, strips URL fragments (hash, e.g., `#section`) from the URL before analysis. | +| Name | Type | Required | Default | Description | +| :---------------------------------- | :------- | :------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | String | Yes | | The name of the module, which is `NeuwoRTDModule`. | +| `params` | Object | Yes | | Container for module-specific parameters. | +| `params.neuwoApiUrl` | String | Yes | | The endpoint URL for the Neuwo Edge API. | +| `params.neuwoApiToken` | String | Yes | | Your unique API token provided by Neuwo. | +| `params.iabContentTaxonomyVersion` | String | No | `'2.2'` | Specifies the version of the IAB Content Taxonomy to be used. Supported values: `'1.0'`, `'2.2'`, `'3.0'`. | +| `params.enableCache` | Boolean | No | `true` | If `true`, caches API responses to avoid redundant requests for the same page during the session. Set to `false` to disable caching and make a fresh API call on every bid request. | +| `params.enableOrtb25Fields` | Boolean | No | `true` | If `true`, populates OpenRTB 2.5 category fields (`ortb2.site.cat`, `ortb2.site.sectioncat`, `ortb2.site.pagecat`, `ortb2.site.content.cat`) with IAB Content Taxonomy 1.0 segments. See [OpenRTB 2.5 Category Fields](#openrtb-25-category-fields) section below for details. | +| `params.stripAllQueryParams` | Boolean | No | `false` | If `true`, strips all query parameters from the URL before analysis. Takes precedence over other stripping options. | +| `params.stripQueryParamsForDomains` | String[] | No | `[]` | List of domains for which to strip **all** query parameters. When a domain matches, all query params are removed for that domain and all its subdomains (e.g., `'example.com'` strips params for both `'example.com'` and `'sub.example.com'`). This option takes precedence over `stripQueryParams` for matching domains. | +| `params.stripQueryParams` | String[] | No | `[]` | List of specific query parameter names to strip from the URL (e.g., `['utm_source', 'fbclid']`). Other parameters are preserved. Only applies when the domain does not match `stripQueryParamsForDomains`. | +| `params.stripFragments` | Boolean | No | `false` | If `true`, strips URL fragments (hash, e.g., `#section`) from the URL before analysis. | +| `params.iabTaxonomyFilters` | Object | No | | Per-tier filtering configuration for IAB taxonomies. Allows filtering by relevance threshold and limiting the count of categories per tier. Filters configured for `ContentTier1` and `ContentTier2` are automatically applied to IAB Content Taxonomy 1.0 when `enableOrtb25Fields` is `true`. See [IAB Taxonomy Filtering](#iab-taxonomy-filtering) section for details. | ### API Response Caching By default, the module caches API responses during the page session to optimise performance and reduce redundant API calls. This behaviour can be disabled by setting `enableCache: false` if needed for dynamic content scenarios. +### OpenRTB 2.5 Category Fields + +The module supports populating OpenRTB 2.5 category fields with IAB Content Taxonomy 1.0 segments. This feature is enabled by default and provides additional contextual signals to bidders through standard OpenRTB fields. + +**Category Fields Populated:** + +- `ortb2.site.cat` - Array of IAB Content Taxonomy 1.0 category IDs +- `ortb2.site.sectioncat` - Array of IAB Content Taxonomy 1.0 category IDs +- `ortb2.site.pagecat` - Array of IAB Content Taxonomy 1.0 category IDs +- `ortb2.site.content.cat` - Array of IAB Content Taxonomy 1.0 category IDs + +**Result Example:** + +With `enableOrtb25Fields: true`, the module injects: + +```javascript +ortb2: { + site: { + // OpenRTB 2.5 category fields + cat: ["IAB12", "IAB12-3", "IAB12-5"], + sectioncat: ["IAB12", "IAB12-3", "IAB12-5"], + pagecat: ["IAB12", "IAB12-3", "IAB12-5"], + content: { + // OpenRTB 2.5 category field + cat: ["IAB12", "IAB12-3", "IAB12-5"], + // Standard content data + data: [{ + name: "www.neuwo.ai", + segment: [{ id: "274" }, { id: "42" }], + ext: { segtax: 6 } + }] + } + } +} +``` + ### URL Cleaning Options The module provides optional URL cleaning capabilities to strip query parameters and/or fragments from the analysed URL before sending it to the Neuwo API. This can be useful for privacy, caching, or analytics purposes. @@ -119,15 +150,15 @@ The module provides optional URL cleaning capabilities to strip query parameters ```javascript pbjs.setConfig({ realTimeData: { - auctionDelay: 500, // Value can be adjusted based on the needs + auctionDelay: 500, // Value can be adjusted based on the needs. Recommended to start with value `500` dataProviders: [ { name: "NeuwoRTDModule", - waitForIt: true, + waitForIt: true, // Recommended to be set to `true` params: { neuwoApiUrl: "", neuwoApiToken: "", - iabContentTaxonomyVersion: "3.0", + iabContentTaxonomyVersion: "2.2", // Option 1: Strip all query parameters from the URL stripAllQueryParams: true, @@ -147,6 +178,133 @@ pbjs.setConfig({ }); ``` +### IAB Taxonomy Filtering + +The module provides optional per-tier filtering for IAB taxonomies to control the quantity and quality of categories injected into bid requests. This allows you to limit categories based on their relevance score and restrict the maximum number of categories per tier. Filtering is performed server-side, which means only the filtered categories are returned in the response. This reduces bandwidth and improves performance. + +**Filter Configuration:** + +Each tier can have two optional parameters: + +- `threshold` (Number): Minimum relevance score (0.0 to 1.0). Categories below this threshold are excluded. +- `limit` (Number): Maximum number of categories to include for this tier (after filtering and sorting by relevance). + +**Available Tiers:** + +| Tier Name | Description | IAB Taxonomy | +| :-------------- | :------------------ | :----------------------------------- | +| `ContentTier1` | IAB Content Tier 1 | Based on configured taxonomy version | +| `ContentTier2` | IAB Content Tier 2 | Based on configured taxonomy version | +| `ContentTier3` | IAB Content Tier 3 | Based on configured taxonomy version | +| `AudienceTier3` | IAB Audience Tier 3 | IAB Audience Taxonomy 1.1 (segtax 4) | +| `AudienceTier4` | IAB Audience Tier 4 | IAB Audience Taxonomy 1.1 (segtax 4) | +| `AudienceTier5` | IAB Audience Tier 5 | IAB Audience Taxonomy 1.1 (segtax 4) | + +**Example with IAB taxonomy filtering:** + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 500, // Value can be adjusted based on the needs. Recommended to start with value `500` + dataProviders: [ + { + name: "NeuwoRTDModule", + waitForIt: true, // Recommended to be set to `true` + params: { + neuwoApiUrl: "", + neuwoApiToken: "", + iabContentTaxonomyVersion: "2.2", + + // Filter IAB taxonomies by tier + iabTaxonomyFilters: { + // Content Tier 1: Keep only the top category with at least 10% relevance + ContentTier1: { limit: 1, threshold: 0.1 }, + + // Content Tier 2: Keep top 2 categories with at least 10% relevance + ContentTier2: { limit: 2, threshold: 0.1 }, + + // Content Tier 3: Keep top 3 categories with at least 15% relevance + ContentTier3: { limit: 3, threshold: 0.15 }, + + // Audience Tier 3: Keep top 3 categories with at least 20% relevance + AudienceTier3: { limit: 3, threshold: 0.2 }, + + // Audience Tier 4: Keep top 5 categories with at least 20% relevance + AudienceTier4: { limit: 5, threshold: 0.2 }, + + // Audience Tier 5: Keep top 7 categories with at least 30% relevance + AudienceTier5: { limit: 7, threshold: 0.3 }, + }, + }, + }, + ], + }, +}); +``` + +**OpenRTB 2.5 Category Fields Filtering** + +When `iabTaxonomyFilters` are configured, the same filters applied to `ContentTier1` and `ContentTier2` are automatically applied to IAB Content Taxonomy 1.0 data used for these fields. Note that IAB Content Taxonomy 1.0 only has tiers 1 and 2, so `ContentTier3` filters are ignored for these fields. + +## Accessing Neuwo Data Outside Prebid.js + +The Neuwo RTD module enriches bid requests with contextual data that can be accessed in application code for analytics, targeting, integration with Google Ad Manager as [Publisher Provided Signals (PPS)](https://support.google.com/admanager/answer/15287325) or other purposes. The enriched data is available through Prebid.js events. + +### Example of Using the `bidRequested` Event + +Listen to the `bidRequested` event to access the enriched ORTB2 data. This event fires early in the auction lifecycle and provides direct access to the Neuwo data: + +```javascript +pbjs.que.push(function () { + pbjs.onEvent("bidRequested", function (bidRequest) { + // The ortb2 data is available directly on the bidRequest + const ortb2 = bidRequest.ortb2; + + // Extract Neuwo-specific data (from www.neuwo.ai provider) + const neuwoSiteData = ortb2?.site?.content?.data?.find( + (d) => d.name === "www.neuwo.ai" + ); + const neuwoUserData = ortb2?.user?.data?.find( + (d) => d.name === "www.neuwo.ai" + ); + + // Extract OpenRTB 2.5 category fields (if enableOrtb25Fields is true) + const categoryFields = { + siteCat: ortb2?.site?.cat, + siteSectioncat: ortb2?.site?.sectioncat, + sitePagecat: ortb2?.site?.pagecat, + contentCat: ortb2?.site?.content?.cat, + }; + + // Use the data in the application + console.log("Neuwo Site Content:", neuwoSiteData); + console.log("Neuwo User Data:", neuwoUserData); + console.log("OpenRTB 2.5 Category Fields:", categoryFields); + + // Example: Store in a global variable for later use + window.neuwoData = { + siteContent: neuwoSiteData, + user: neuwoUserData, + categoryFields: categoryFields, + }; + }); +}); +``` + +### Other Prebid.js Events + +The Neuwo data is also available in other Prebid.js events: + +| Order | Event | Fires Once Per | Data Location | +| :---- | :----------------- | :------------- | :----------------------------------- | +| 1 | `auctionInit` | Auction | `auctionData.bidderRequests[].ortb2` | +| 2 | `bidRequested` | Bidder | `bidRequest.ortb2` | +| 3 | `beforeBidderHttp` | Bidder | `bidRequests[].ortb2` | +| 4 | `bidResponse` | Bidder | `bidResponse.ortb2` | +| 5 | `auctionEnd` | Auction | `auctionData.bidderRequests[].ortb2` | + +For more information on Prebid.js events, see the [Prebid.js Event API documentation](https://docs.prebid.org/dev-docs/publisher-api-reference/getEvents.html). + ## Local Development Install the exact versions of packages specified in the lockfile: @@ -194,7 +352,7 @@ npx eslint 'modules/neuwoRtdProvider.js' --cache --cache-strategy content To run the module-specific tests: ```bash -npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/euwoRtdProvider_spec.js +npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/neuwoRtdProvider_spec.js ``` Skip building, if the project has already been built: @@ -202,3 +360,32 @@ Skip building, if the project has already been built: ```bash npx gulp test-only-nobuild --file=test/spec/modules/neuwoRtdProvider_spec.js ``` + +To generate test coverage report for the Neuwo RTD Module: + +```bash +npx gulp test-coverage --file=test/spec/modules/neuwoRtdProvider_spec.js +``` + +After running the coverage command, you can view the HTML report: + +```bash +# Open the coverage report in your browser +firefox build/coverage/lcov-report/index.html +# or +google-chrome build/coverage/lcov-report/index.html +``` + +Navigate to `modules/neuwoRtdProvider.js` in the report to see detailed line-by-line coverage with highlighted covered/uncovered lines. + +## Building for Production + +To generate minified code for production use: + +```bash +npx gulp build --modules=rtdModule,neuwoRtdProvider +``` + +This command creates optimised, minified code typically used on websites. + +> Version **2.2.6** diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 09472759521..ec089e151aa 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -22,8 +22,9 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; import { getViewportSize } from '../libraries/viewport/viewport.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; -const NM_VERSION = '4.5.0'; +const NM_VERSION = '4.5.1'; const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; @@ -148,6 +149,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { + const bidIds = new Map() const requests = []; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; const site = getSiteObj(); @@ -180,10 +182,14 @@ export const spec = { const id = getPlacementId(bid); const {cur, mediaTypes} = getCurrency(bid); if (i === 0) postBody.cur = cur; - const imp = getImp(bid, id, mediaTypes); + + const impId = String(i + 1) + bidIds.set(impId, bid.bidId) + + const imp = getImp(impId, bid, id, mediaTypes); setOrtb2Parameters(ALLOWED_ORTB2_IMP_PARAMETERS, imp, bid?.ortb2Imp); postBody.imp.push(imp); - postBody.ext.next_mil_imps.push(getExtNextMilImp(bid)); + postBody.ext.next_mil_imps.push(getExtNextMilImp(impId, bid)); }); this.getUrlPixelMetric(EVENTS.BID_REQUESTED, validBidRequests); @@ -196,19 +202,21 @@ export const spec = { contentType: 'text/plain', withCredentials: true, }, + + bidIds, }); return requests; }, - interpretResponse: function(serverResponse) { + interpretResponse: function(serverResponse, bidRequest) { const response = serverResponse.body; const bidResponses = []; const bids = []; _each(response.seatbid, (resp) => { _each(resp.bid, (bid) => { - const requestId = bid.impid; + const requestId = bidRequest.bidIds.get(bid.impid); const {ad, adUrl, vastUrl, vastXml} = getAd(bid); @@ -327,11 +335,11 @@ export const spec = { }, }; -export function getExtNextMilImp(bid) { +export function getExtNextMilImp(impId, bid) { if (typeof window?.nmmRefreshCounts[bid.adUnitCode] === 'number') ++window.nmmRefreshCounts[bid.adUnitCode]; const {adSlots, allowedAds} = bid.params const nextMilImp = { - impId: bid.bidId, + impId, nextMillennium: { nm_version: NM_VERSION, pbjs_version: PBJS_VERSION, @@ -346,10 +354,10 @@ export function getExtNextMilImp(bid) { return nextMilImp; } -export function getImp(bid, id, mediaTypes) { +export function getImp(impId, bid, id, mediaTypes) { const {banner, video} = mediaTypes; const imp = { - id: bid.bidId, + id: impId, ext: { prebid: { storedrequest: { @@ -576,14 +584,16 @@ function getDeviceObj() { } function getDeviceConnectionType() { - const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - if (connection?.type === 'ethernet') return 1; - if (connection?.type === 'wifi') return 2; - - if (connection?.effectiveType === 'slow-2g') return 3; - if (connection?.effectiveType === '2g') return 4; - if (connection?.effectiveType === '3g') return 5; - if (connection?.effectiveType === '4g') return 6; + const connection = getConnectionInfo(); + const connectionType = connection?.type; + const effectiveType = connection?.effectiveType; + if (connectionType === 'ethernet') return 1; + if (connectionType === 'wifi') return 2; + + if (effectiveType === 'slow-2g') return 3; + if (effectiveType === '2g') return 4; + if (effectiveType === '3g') return 5; + if (effectiveType === '4g') return 6; return undefined; } diff --git a/modules/nexx360BidAdapter.ts b/modules/nexx360BidAdapter.ts index b5c27bd288b..a5aa9e92dd7 100644 --- a/modules/nexx360BidAdapter.ts +++ b/modules/nexx360BidAdapter.ts @@ -59,9 +59,9 @@ const ALIASES = [ { code: 'scoremedia', gvlid: 965 }, { code: 'movingup', gvlid: 1416 }, { code: 'glomexbidder', gvlid: 967 }, - { code: 'revnew', gvlid: 1468 }, { code: 'pubxai', gvlid: 1485 }, { code: 'ybidder', gvlid: 1253 }, + { code: 'netads', gvlid: 965 }, ]; export const STORAGE = getStorageManager({ diff --git a/modules/nodalsAiRtdProvider.js b/modules/nodalsAiRtdProvider.js index 8cf0df0bde2..104e91ab851 100644 --- a/modules/nodalsAiRtdProvider.js +++ b/modules/nodalsAiRtdProvider.js @@ -11,7 +11,7 @@ const GVLID = 1360; const ENGINE_VESION = '1.x.x'; const PUB_ENDPOINT_ORIGIN = 'https://nodals.io'; const LOCAL_STORAGE_KEY = 'signals.nodals.ai'; -const STORAGE_TTL = 3600; // 1 hour in seconds +const DEFAULT_STORAGE_TTL = 3600; // 1 hour in seconds const fillTemplate = (strings, ...keys) => { return function (values) { @@ -204,7 +204,11 @@ class NodalsAiRtdProvider { } #getEngine() { - return window?.$nodals?.adTargetingEngine[ENGINE_VESION]; + try { + return window?.$nodals?.adTargetingEngine?.[ENGINE_VESION]; + } catch (error) { + return undefined; + } } #setOverrides(params) { @@ -320,7 +324,7 @@ class NodalsAiRtdProvider { #dataIsStale(dataEnvelope) { const currentTime = Date.now(); const dataTime = dataEnvelope.createdAt || 0; - const staleThreshold = this.#overrides?.storageTTL ?? dataEnvelope?.data?.meta?.ttl ?? STORAGE_TTL; + const staleThreshold = this.#overrides?.storageTTL ?? dataEnvelope?.data?.meta?.ttl ?? DEFAULT_STORAGE_TTL; return currentTime - dataTime >= (staleThreshold * 1000); } diff --git a/modules/nubaBidAdapter.js b/modules/nubaBidAdapter.js index 0ebfe715508..6692a13bb1d 100644 --- a/modules/nubaBidAdapter.js +++ b/modules/nubaBidAdapter.js @@ -4,7 +4,7 @@ import { isBidRequestValid, buildRequests, interpretResponse } from '../librarie const BIDDER_CODE = 'nuba'; -const AD_URL = 'https://ads.nuba.io/openrtb2/auction'; +const AD_URL = 'https://ads.nuba.io/pbjs'; export const spec = { code: BIDDER_CODE, @@ -12,8 +12,7 @@ export const spec = { isBidRequestValid: isBidRequestValid(), buildRequests: buildRequests(AD_URL), - interpretResponse, - getUserSyncs: () => {}, + interpretResponse }; registerBidder(spec); diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index eed8fae088d..00c8bfb77ef 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -2,6 +2,7 @@ import { BANNER } from '../src/mediaTypes.js'; import { getWindowSelf, getWindowTop, isFn, deepAccess, isPlainObject, deepSetValue, mergeDeep } from '../src/utils.js'; +import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { ajax } from '../src/ajax.js'; import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; @@ -13,7 +14,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 = '2.0.4'; +const ADAPTER_VERSION = '2.0.5'; export const ortbConverterProps = { context: { @@ -25,7 +26,7 @@ export const ortbConverterProps = { request(buildRequest, imps, bidderRequest, context) { const req = buildRequest(imps, bidderRequest, context); req.tmax = DEFAULT_TIMEOUT; - deepSetValue(req, 'device.pxratio', window.devicePixelRatio); + deepSetValue(req, 'device.pxratio', getDevicePixelRatio(getWindowContext())); deepSetValue(req, 'site.page', getWindowContext().location.href); req.ext = mergeDeep({}, req.ext, { @@ -99,15 +100,7 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gpp return [ { type: 'image', - 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&gpp=${gpp}&gpp_sid=${gppSid}` - }, - { - type: 'image', - url: `${MS_COOKIE_SYNC_DOMAIN}/xandr/init-sync?iab_string=${consent}&source=prebid&gpp=${gpp}&gpp_sid=${gppSid}` + url: `${MS_COOKIE_SYNC_DOMAIN}/user-sync?source=prebid&gdpr_consent=${consent}&gpp=${gpp}&gpp_sid=${gppSid}` } ]; } diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 68d93a123f7..9eab71254d3 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -1,21 +1,18 @@ import { - isArray, - getWindowTop, deepSetValue, logError, logWarn, - createTrackPixelHtml, getBidIdParameter, getUniqueIdentifierStr, formatQS, + deepAccess, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; -import {percentInView} from '../libraries/percentInView/percentInView.js'; import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; -import {getMinSize} from '../libraries/sizeUtils/sizeUtils.js'; -import {getBidFloor, isIframe} from '../libraries/omsUtils/index.js'; +import {getAdMarkup, getBidFloor, getDeviceType, getProcessedSizes} from '../libraries/omsUtils/index.js'; +import {getRoundedViewability} from '../libraries/omsUtils/viewability.js'; const BIDDER_CODE = 'oms'; const URL = 'https://rt.marphezis.com/hb'; @@ -38,19 +35,14 @@ export const spec = { function buildRequests(bidReqs, bidderRequest) { try { const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid?.mediaTypes?.video?.playerSize || 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 bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes || []; + const processedSizes = getProcessedSizes(bidSizes); + const viewabilityAmountRounded = getRoundedViewability(bid.adUnitCode, processedSizes); const gpidData = _extractGpidData(bid); const imp = { id: bid.bidId, + displaymanagerver: '$prebid.version$', ext: { ...gpidData }, @@ -72,6 +64,10 @@ function buildRequests(bidReqs, bidderRequest) { } } + if (deepAccess(bid, 'ortb2Imp.instl') === 1) { + imp.instl = 1; + } + const bidFloor = getBidFloor(bid); if (bidFloor) { @@ -95,7 +91,7 @@ function buildRequests(bidReqs, bidderRequest) { } }, device: { - devicetype: _getDeviceType(navigator.userAgent, bidderRequest?.ortb2?.device?.sua), + devicetype: getDeviceType(navigator.userAgent, bidderRequest?.ortb2?.device?.sua), w: screen.width, h: screen.height }, @@ -175,7 +171,6 @@ function interpretResponse(serverResponse) { creativeId: bid.crid || bid.id, currency: 'USD', netRevenue: true, - ad: _getAdMarkup(bid), ttl: 300, meta: { advertiserDomains: bid?.adomain || [] @@ -184,8 +179,10 @@ function interpretResponse(serverResponse) { if (bid.mtype === 2) { bidResponse.mediaType = VIDEO; + bidResponse.vastXml = bid.adm; } else { bidResponse.mediaType = BANNER; + bidResponse.ad = getAdMarkup(bid); } return bidResponse; @@ -237,18 +234,6 @@ function _trackEvent(endpoint, data) { }); } -function _getDeviceType(ua, sua) { - if (sua?.mobile || (/(ios|ipod|ipad|iphone|android)/i).test(ua)) { - return 1 - } - - if ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(ua)) { - return 3 - } - - return 2 -} - function _getGpp(bidderRequest) { if (bidderRequest?.gppConsent != null) { return bidderRequest.gppConsent; @@ -259,22 +244,6 @@ function _getGpp(bidderRequest) { ); } -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' ? percentInView(element, {w, h}) : 0; -} - function _extractGpidData(bid) { return { gpid: bid?.ortb2Imp?.ext?.gpid, diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index e2da98a67be..d919d1398b7 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -8,6 +8,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { deepClone, logError, deepAccess, getWinDimensions } from '../src/utils.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { toOrtbNativeRequest } from '../src/native.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -81,14 +82,14 @@ export function isValid(type, bid) { return false; } -const isValidEventTracker = function(et) { +const isValidEventTracker = function (et) { if (!et.event || !et.methods || !Number.isInteger(et.event) || !Array.isArray(et.methods) || !et.methods.length > 0) { return false; } return true; } -const isValidAsset = function(asset) { +const isValidAsset = function (asset) { if (!asset.hasOwnProperty("id") || !Number.isInteger(asset.id)) return false; const hasValidContent = asset.title || asset.img || asset.data || asset.video; if (!hasValidContent) return false; @@ -141,9 +142,9 @@ function buildRequests(validBidRequests, bidderRequest) { payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); } } catch (e) { } - const connection = navigator.connection || navigator.webkitConnection; - payload.networkConnectionType = (connection && connection.type) ? connection.type : null; - payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null; + const connection = getConnectionInfo(); + payload.networkConnectionType = connection?.type || null; + payload.networkEffectiveConnectionType = connection?.effectiveType || null; payload.fledgeEnabled = Boolean(bidderRequest?.paapi?.enabled) return { method: 'POST', @@ -209,7 +210,8 @@ function interpretResponse(serverResponse, bidderRequest) { const fledgeAuctionConfigs = body.fledgeAuctionConfigs return { bids, - paapi: fledgeAuctionConfigs} + paapi: fledgeAuctionConfigs + } } else { return bids; } @@ -288,8 +290,6 @@ function getPageInfo(bidderRequest) { wHeight: winDimensions.innerHeight, sWidth: winDimensions.screen.width, sHeight: winDimensions.screen.height, - sLeft: 'screenLeft' in topmostFrame ? topmostFrame.screenLeft : topmostFrame.screenX, - sTop: 'screenTop' in topmostFrame ? topmostFrame.screenTop : topmostFrame.screenY, xOffset: topmostFrame.pageXOffset, yOffset: topmostFrame.pageYOffset, docHidden: getDocumentVisibility(topmostFrame), @@ -298,7 +298,7 @@ function getPageInfo(bidderRequest) { timing: getTiming(), version: { prebid: '$prebid.version$', - adapter: '1.1.5' + adapter: '1.1.6' } }; } @@ -482,7 +482,7 @@ function getBidFloor(bidRequest, mediaType, sizes) { return { ...floorData, - size: size && size.length === 2 ? {width: size[0], height: size[1]} : null, + size: size && size.length === 2 ? { width: size[0], height: size[1] } : null, floor: floorData.floor != null ? floorData.floor : null }; }; diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index c3176d7abcc..f15ef2e63b1 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -1,17 +1,14 @@ import { _each, - createTrackPixelHtml, getBidIdParameter, + getBidIdParameter, getUniqueIdentifierStr, - getWindowTop, - isArray, logError, logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import { percentInView } from '../libraries/percentInView/percentInView.js'; -import {getMinSize} from '../libraries/sizeUtils/sizeUtils.js'; -import {getBidFloor, isIframe} from '../libraries/omsUtils/index.js'; +import {getAdMarkup, getBidFloor, getDeviceType, getProcessedSizes} from '../libraries/omsUtils/index.js'; +import {getRoundedViewability} from '../libraries/omsUtils/viewability.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -34,17 +31,9 @@ function buildRequests(bidReqs, bidderRequest) { const onomagicImps = []; 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 bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + const processedSizes = getProcessedSizes(bidSizes); + const viewabilityAmountRounded = getRoundedViewability(bid.adUnitCode, processedSizes); const imp = { id: bid.bidId, @@ -74,7 +63,7 @@ function buildRequests(bidReqs, bidderRequest) { } }, device: { - devicetype: _getDeviceType(), + devicetype: getDeviceType(), w: screen.width, h: screen.height }, @@ -127,7 +116,7 @@ function interpretResponse(serverResponse) { currency: 'USD', netRevenue: true, mediaType: BANNER, - ad: _getAdMarkup(onomagicBid), + ad: getAdMarkup(onomagicBid), ttl: 60, meta: { advertiserDomains: onomagicBid && onomagicBid.adomain ? onomagicBid.adomain : [] @@ -146,34 +135,4 @@ 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' - ? percentInView(element, { w, h }) - : 0; -} - registerBidder(spec); diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index f82e0337e7f..8645196d07d 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -28,8 +28,8 @@ import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; */ const BIDDER_CODE = 'operaads'; -const ENDPOINT = 'https://s.adx.opera.com/ortb/v2/'; -const USER_SYNC_ENDPOINT = 'https://s.adx.opera.com/usersync/page'; +const ENDPOINT = 'https://s.oa.opera.com/ortb/v2/'; +const USER_SYNC_ENDPOINT = 'https://s.oa.opera.com/usersync/page'; const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; diff --git a/modules/optableRtdProvider.js b/modules/optableRtdProvider.js index 2ef71ce9d44..29638ba3a94 100644 --- a/modules/optableRtdProvider.js +++ b/modules/optableRtdProvider.js @@ -17,6 +17,7 @@ export const parseConfig = (moduleConfig) => { let bundleUrl = deepAccess(moduleConfig, 'params.bundleUrl', null); const adserverTargeting = deepAccess(moduleConfig, 'params.adserverTargeting', true); const handleRtd = deepAccess(moduleConfig, 'params.handleRtd', null); + const instance = deepAccess(moduleConfig, 'params.instance', null); // If present, trim the bundle URL if (typeof bundleUrl === 'string') { @@ -25,18 +26,51 @@ export const parseConfig = (moduleConfig) => { // Verify that bundleUrl is a valid URL: only secure (HTTPS) URLs are allowed if (typeof bundleUrl === 'string' && bundleUrl.length && !bundleUrl.startsWith('https://')) { - throw new Error( - LOG_PREFIX + ' Invalid URL format for bundleUrl in moduleConfig. Only HTTPS URLs are allowed.' - ); + logError('Invalid URL format for bundleUrl in moduleConfig. Only HTTPS URLs are allowed.'); + return {bundleUrl: null, adserverTargeting, handleRtd: null}; } if (handleRtd && typeof handleRtd !== 'function') { - throw new Error(LOG_PREFIX + ' handleRtd must be a function'); + logError('handleRtd must be a function'); + return {bundleUrl, adserverTargeting, handleRtd: null}; } - return {bundleUrl, adserverTargeting, handleRtd}; + const result = {bundleUrl, adserverTargeting, handleRtd}; + if (instance !== null) { + result.instance = instance; + } + return result; } +/** + * Wait for Optable SDK event to fire with targeting data + * @param {string} eventName Name of the event to listen for + * @returns {Promise} Promise that resolves with targeting data or null + */ +const waitForOptableEvent = (eventName) => { + return new Promise((resolve) => { + const optableBundle = /** @type {Object} */ (window.optable); + const cachedData = optableBundle?.instance?.targetingFromCache(); + + if (cachedData && cachedData.ortb2) { + logMessage('Optable SDK already has cached data'); + resolve(cachedData); + return; + } + + const eventListener = (event) => { + logMessage(`Received ${eventName} event`); + // Extract targeting data from event detail + const targetingData = event.detail; + window.removeEventListener(eventName, eventListener); + resolve(targetingData); + }; + + window.addEventListener(eventName, eventListener); + logMessage(`Waiting for ${eventName} event`); + }); +}; + /** * Default function to handle/enrich RTD data * @param reqBidsConfigObj Bid request configuration object @@ -45,15 +79,8 @@ export const parseConfig = (moduleConfig) => { * @returns {Promise} */ export const defaultHandleRtd = async (reqBidsConfigObj, optableExtraData, mergeFn) => { - const optableBundle = /** @type {Object} */ (window.optable); - // Get targeting data from cache, if available - let targetingData = optableBundle?.instance?.targetingFromCache(); - // If no targeting data is found in the cache, call the targeting function - if (!targetingData) { - // Call Optable DCN for targeting data and return the ORTB2 object - targetingData = await optableBundle?.instance?.targeting(); - } - logMessage('Original targeting data from targeting(): ', targetingData); + // Wait for the Optable SDK to dispatch targeting data via event + let targetingData = await waitForOptableEvent('optable-targeting:change'); if (!targetingData || !targetingData.ortb2) { logWarn('No targeting data found'); @@ -92,7 +119,6 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user try { // Extract the bundle URL from the module configuration const {bundleUrl, handleRtd} = parseConfig(moduleConfig); - const handleRtdFn = handleRtd || defaultHandleRtd; const optableExtraData = config.getConfig('optableRtdConfig') || {}; @@ -135,8 +161,8 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user * @returns {Object} Targeting data */ export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) => { - // Extract `adserverTargeting` from the module configuration - const {adserverTargeting} = parseConfig(moduleConfig); + // Extract `adserverTargeting` and `instance` from the module configuration + const {adserverTargeting, instance} = parseConfig(moduleConfig); logMessage('Ad Server targeting: ', adserverTargeting); if (!adserverTargeting) { @@ -145,9 +171,17 @@ export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) => } const targetingData = {}; + // Resolve the SDK instance object based on the instance string + // Default to 'instance' if not provided + const instanceKey = instance || 'instance'; + const sdkInstance = window?.optable?.[instanceKey]; + if (!sdkInstance) { + logWarn(`No Optable SDK instance found for: ${instanceKey}`); + return targetingData; + } // Get the Optable targeting data from the cache - const optableTargetingData = window?.optable?.instance?.targetingKeyValuesFromCache() || {}; + const optableTargetingData = sdkInstance?.targetingKeyValuesFromCache?.() || targetingData; // If no Optable targeting data is found, return an empty object if (!Object.keys(optableTargetingData).length) { diff --git a/modules/optableRtdProvider.md b/modules/optableRtdProvider.md index 45fc7d589d7..4ac0d4541f4 100644 --- a/modules/optableRtdProvider.md +++ b/modules/optableRtdProvider.md @@ -46,7 +46,8 @@ pbjs.setConfig({ { name: 'optable', params: { - adserverTargeting: '', + adserverTargeting: true, // optional, true by default, set to true to also set GAM targeting keywords to ad slots + instance: window.optable.rtd.instance, // optional, defaults to window.optable.rtd.instance if not specified }, }, ], @@ -56,12 +57,13 @@ pbjs.setConfig({ ### Parameters -| Name | Type | Description | Default | Notes | -|--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|----------| -| name | String | Real time data module name | Always `optable` | | -| params | Object | | | | -| params.adserverTargeting | Boolean | If set to `true`, targeting keywords will be passed to the ad server upon auction completion | `true` | Optional | -| params.handleRtd | Function | An optional function that uses Optable data to enrich `reqBidsConfigObj` with the real-time data. If not provided, the module will do a default call to Optable bundle. The function signature is `[async] (reqBidsConfigObj, optableExtraData, mergeFn) => {}` | `null` | Optional | +| Name | Type | Description | Default | Notes | +|--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------| +| name | String | Real time data module name | Always `optable` | | +| params | Object | | | | +| params.adserverTargeting | Boolean | If set to `true`, targeting keywords will be passed to the ad server upon auction completion | `true` | Optional | +| params.instance | Object | Optable SDK instance to use for targeting data. | `window.optable.rtd.instance` | Optional | +| params.handleRtd | Function | An optional function that uses Optable data to enrich `reqBidsConfigObj` with the real-time data. If not provided, the module will do a default call to Optable bundle. The function signature is `[async] (reqBidsConfigObj, optableExtraData, mergeFn) => {}` | `null` | Optional | ## Publisher Customized RTD Handler Function diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js index 1330bf2054f..6529f02b5bc 100755 --- a/modules/optidigitalBidAdapter.js +++ b/modules/optidigitalBidAdapter.js @@ -63,7 +63,8 @@ export const spec = { imp: validBidRequests.map(bidRequest => buildImp(bidRequest, ortb2)), badv: ortb2.badv || deepAccess(validBidRequests[0], 'params.badv') || [], bcat: ortb2.bcat || deepAccess(validBidRequests[0], 'params.bcat') || [], - bapp: deepAccess(validBidRequests[0], 'params.bapp') || [] + bapp: deepAccess(validBidRequests[0], 'params.bapp') || [], + device: ortb2.device || {} } if (validBidRequests[0].auctionId) { @@ -82,10 +83,14 @@ export const spec = { const gdpr = deepAccess(bidderRequest, 'gdprConsent'); if (bidderRequest && gdpr) { const isConsentString = typeof gdpr.consentString === 'string'; + const isGdprApplies = typeof gdpr.gdprApplies === 'boolean'; payload.gdpr = { consent: isConsentString ? gdpr.consentString : '', - required: true + required: isGdprApplies ? gdpr.gdprApplies : false }; + if (gdpr?.addtlConsent) { + payload.gdpr.addtlConsent = gdpr.addtlConsent; + } } if (bidderRequest && !gdpr) { payload.gdpr = { @@ -120,10 +125,16 @@ export const spec = { } } + const ortb2SiteKeywords = (bidderRequest?.ortb2?.site?.keywords || '')?.split(',').map(k => k.trim()).filter(k => k !== '').join(','); + if (ortb2SiteKeywords) { + payload.site = payload.site || {}; + payload.site.keywords = ortb2SiteKeywords; + } + const payloadObject = JSON.stringify(payload); return { method: 'POST', - url: ENDPOINT_URL, + url: `${ENDPOINT_URL}/${payload.publisherId}`, data: payloadObject }; }, @@ -230,6 +241,11 @@ function buildImp(bidRequest, ortb2) { imp.battr = battr; } + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + if (gpid) { + imp.gpid = gpid; + } + return imp; } diff --git a/modules/panxoBidAdapter.js b/modules/panxoBidAdapter.js new file mode 100644 index 00000000000..6f9eb80ae84 --- /dev/null +++ b/modules/panxoBidAdapter.js @@ -0,0 +1,357 @@ +/** + * @module modules/panxoBidAdapter + * @description Bid Adapter for Prebid.js - AI-referred traffic monetization + * @see https://docs.panxo.ai for Signal script installation + */ + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, logWarn, isFn, isPlainObject } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'panxo'; +const ENDPOINT_URL = 'https://panxo-sys.com/openrtb/2.5/bid'; +const USER_ID_KEY = 'panxo_uid'; +const SYNC_URL = 'https://panxo-sys.com/usersync'; +const DEFAULT_CURRENCY = 'USD'; +const TTL = 300; +const NET_REVENUE = true; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export function getPanxoUserId() { + try { + return storage.getDataFromLocalStorage(USER_ID_KEY); + } catch (e) { + // storageManager handles errors internally + } + return null; +} + +function buildBanner(bid) { + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes || []; + if (sizes.length === 0) return null; + + return { + format: sizes.map(size => ({ w: size[0], h: size[1] })), + w: sizes[0][0], + h: sizes[0][1] + }; +} + +function getFloorPrice(bid, size) { + if (isFn(bid.getFloor)) { + try { + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: BANNER, + size: size + }); + if (floorInfo && floorInfo.floor) { + return floorInfo.floor; + } + } catch (e) { + // Floor module error + } + } + return deepAccess(bid, 'params.floor') || 0; +} + +function buildUser(panxoUid, bidderRequest) { + const user = { buyeruid: panxoUid }; + + // GDPR consent + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); + if (gdprConsent && gdprConsent.consentString) { + user.ext = { consent: gdprConsent.consentString }; + } + + // First Party Data - user + const fpd = deepAccess(bidderRequest, 'ortb2.user'); + if (isPlainObject(fpd)) { + user.ext = { ...user.ext, ...fpd.ext }; + if (fpd.data) user.data = fpd.data; + } + + return user; +} + +function buildRegs(bidderRequest) { + const regs = { ext: {} }; + + // GDPR - only set when gdprApplies is explicitly true or false, not undefined + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); + if (gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + } + + // CCPA / US Privacy + const uspConsent = deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + // GPP + const gppConsent = deepAccess(bidderRequest, 'gppConsent'); + if (gppConsent) { + regs.ext.gpp = gppConsent.gppString; + regs.ext.gpp_sid = gppConsent.applicableSections; + } + + // COPPA + const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa'); + if (coppa) { + regs.coppa = 1; + } + + return regs; +} + +function buildDevice() { + const device = { + ua: navigator.userAgent, + language: navigator.language, + js: 1, + dnt: navigator.doNotTrack === '1' ? 1 : 0 + }; + + if (typeof screen !== 'undefined') { + device.w = screen.width; + device.h = screen.height; + } + + return device; +} + +function buildSite(bidderRequest) { + const site = { + page: deepAccess(bidderRequest, 'refererInfo.page') || '', + domain: deepAccess(bidderRequest, 'refererInfo.domain') || '', + ref: deepAccess(bidderRequest, 'refererInfo.ref') || '' + }; + + // First Party Data - site + const fpd = deepAccess(bidderRequest, 'ortb2.site'); + if (isPlainObject(fpd)) { + Object.assign(site, { + name: fpd.name, + cat: fpd.cat, + sectioncat: fpd.sectioncat, + pagecat: fpd.pagecat, + content: fpd.content + }); + if (fpd.ext) site.ext = fpd.ext; + } + + return site; +} + +function buildSource(bidderRequest) { + const source = { + tid: deepAccess(bidderRequest, 'ortb2.source.tid') || bidderRequest.auctionId + }; + + // Supply Chain (schain) - read from ortb2 where Prebid normalizes it + const schain = deepAccess(bidderRequest, 'ortb2.source.ext.schain'); + if (isPlainObject(schain)) { + source.ext = { schain: schain }; + } + + return source; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: 1527, // IAB TCF Global Vendor List ID + supportedMediaTypes: [BANNER], + + isBidRequestValid(bid) { + const propertyKey = deepAccess(bid, 'params.propertyKey'); + if (!propertyKey) { + logWarn('Panxo: Missing required param "propertyKey"'); + return false; + } + if (!deepAccess(bid, 'mediaTypes.banner')) { + logWarn('Panxo: Only banner mediaType is supported'); + return false; + } + return true; + }, + + buildRequests(validBidRequests, bidderRequest) { + const panxoUid = getPanxoUserId(); + if (!panxoUid) { + logWarn('Panxo: panxo_uid not found. Ensure Signal script is loaded before Prebid.'); + return []; + } + + // Group bids by propertyKey to handle multiple properties on same page + const bidsByPropertyKey = {}; + validBidRequests.forEach(bid => { + const key = deepAccess(bid, 'params.propertyKey'); + if (!bidsByPropertyKey[key]) { + bidsByPropertyKey[key] = []; + } + bidsByPropertyKey[key].push(bid); + }); + + // Build one request per propertyKey + const requests = []; + Object.keys(bidsByPropertyKey).forEach(propertyKey => { + const bidsForKey = bidsByPropertyKey[propertyKey]; + + const impressions = bidsForKey.map((bid) => { + const banner = buildBanner(bid); + if (!banner) return null; + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') || []; + const primarySize = sizes[0] || [300, 250]; + + // Build impression object + const imp = { + id: bid.bidId, + banner: banner, + bidfloor: getFloorPrice(bid, primarySize), + bidfloorcur: DEFAULT_CURRENCY, + secure: 1, + tagid: bid.adUnitCode + }; + + // Merge full ortb2Imp object (instl, pmp, ext, etc.) + const ortb2Imp = deepAccess(bid, 'ortb2Imp'); + if (isPlainObject(ortb2Imp)) { + Object.keys(ortb2Imp).forEach(key => { + if (key === 'ext') { + imp.ext = { ...imp.ext, ...ortb2Imp.ext }; + } else if (imp[key] === undefined) { + imp[key] = ortb2Imp[key]; + } + }); + } + + return imp; + }).filter(Boolean); + + if (impressions.length === 0) return; + + const openrtbRequest = { + id: bidderRequest.bidderRequestId, + imp: impressions, + site: buildSite(bidderRequest), + device: buildDevice(), + user: buildUser(panxoUid, bidderRequest), + regs: buildRegs(bidderRequest), + source: buildSource(bidderRequest), + at: 1, + cur: [DEFAULT_CURRENCY], + tmax: bidderRequest.timeout || 1000 + }; + + requests.push({ + method: 'POST', + url: `${ENDPOINT_URL}?key=${encodeURIComponent(propertyKey)}&source=prebid`, + data: openrtbRequest, + options: { contentType: 'text/plain', withCredentials: false }, + bidderRequest: bidderRequest + }); + }); + + return requests; + }, + + interpretResponse(serverResponse, request) { + const bids = []; + const response = serverResponse.body; + + if (!response || !response.seatbid) return bids; + + const bidRequestMap = {}; + if (request.bidderRequest && request.bidderRequest.bids) { + request.bidderRequest.bids.forEach(bid => { + bidRequestMap[bid.bidId] = bid; + }); + } + + response.seatbid.forEach(seatbid => { + if (!seatbid.bid) return; + + seatbid.bid.forEach(bid => { + const originalBid = bidRequestMap[bid.impid]; + if (!originalBid) return; + + bids.push({ + requestId: bid.impid, + cpm: bid.price, + currency: response.cur || DEFAULT_CURRENCY, + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid || null, + netRevenue: NET_REVENUE, + ttl: bid.exp || TTL, + ad: bid.adm, + nurl: bid.nurl, + meta: { + advertiserDomains: bid.adomain || [], + mediaType: BANNER + } + }); + }); + }); + + return bids; + }, + + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + + if (syncOptions.pixelEnabled) { + let syncUrl = SYNC_URL + '?source=prebid'; + + // GDPR - only include when gdprApplies is explicitly true or false + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + } + if (gdprConsent.consentString) { + syncUrl += `&gdpr_consent=${encodeURIComponent(gdprConsent.consentString)}`; + } + } + + // US Privacy + if (uspConsent) { + syncUrl += `&us_privacy=${encodeURIComponent(uspConsent)}`; + } + + // GPP + if (gppConsent) { + if (gppConsent.gppString) { + syncUrl += `&gpp=${encodeURIComponent(gppConsent.gppString)}`; + } + if (gppConsent.applicableSections) { + syncUrl += `&gpp_sid=${encodeURIComponent(gppConsent.applicableSections.join(','))}`; + } + } + + syncs.push({ type: 'image', url: syncUrl }); + } + + return syncs; + }, + + onBidWon(bid) { + if (bid.nurl) { + const winUrl = bid.nurl.replace(/\$\{AUCTION_PRICE\}/g, bid.cpm); + const img = document.createElement('img'); + img.src = winUrl; + img.style.display = 'none'; + document.body.appendChild(img); + } + }, + + onTimeout(timeoutData) { + logWarn('Panxo: Bid timeout', timeoutData); + } +}; + +registerBidder(spec); diff --git a/modules/panxoBidAdapter.md b/modules/panxoBidAdapter.md new file mode 100644 index 00000000000..17a1df185f9 --- /dev/null +++ b/modules/panxoBidAdapter.md @@ -0,0 +1,229 @@ +# Overview + +``` +Module Name: Panxo Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@panxo.ai +``` + +# Description + +Panxo is a specialized SSP for AI-referred traffic monetization. This adapter enables publishers to monetize traffic coming from AI assistants like ChatGPT, Perplexity, Claude, and Gemini through Prebid.js header bidding. + +**Important**: This adapter requires the Panxo Signal script to be installed on the publisher's page. The Signal script must load before Prebid.js to ensure proper user identification and AI traffic detection. + +# Prerequisites + +1. Register your property at [app.panxo.ai](https://app.panxo.ai) +2. Obtain your `propertyKey` from the Panxo dashboard +3. Install the Panxo Signal script in your page's ``: + +```html + +``` + +# Bid Params + +| Name | Scope | Description | Example | Type | +|------|-------|-------------|---------|------| +| `propertyKey` | required | Your unique property identifier from Panxo dashboard | `'abc123def456'` | `string` | +| `floor` | optional | Minimum CPM floor price in USD | `0.50` | `number` | + +# Configuration Example + +```javascript +var adUnits = [{ + code: 'banner-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + bids: [{ + bidder: 'panxo', + params: { + propertyKey: 'your-property-key-here' + } + }] +}]; +``` + +# Full Page Example + +```html + + + + + + + + + + + + +

+ + +``` + +# Supported Media Types + +| Type | Support | +|------|---------| +| Banner | Yes | +| Video | No | +| Native | No | + +# Privacy & Consent + +**IAB TCF Global Vendor List ID: 1527** + +This adapter is registered with the IAB Europe Transparency and Consent Framework. Publishers using a CMP (Consent Management Platform) should ensure Panxo (Vendor ID 1527) is included in their vendor list. + +This adapter supports: + +- **GDPR/TCF 2.0**: Consent string is passed in bid requests. GVL ID: 1527 +- **CCPA/US Privacy**: USP string is passed in bid requests +- **GPP**: Global Privacy Platform strings are supported +- **COPPA**: Child-directed content flags are respected + +## CMP Configuration + +If you use a Consent Management Platform (Cookiebot, OneTrust, Quantcast Choice, etc.), ensure that: + +1. Panxo (Vendor ID: 1527) is included in your vendor list +2. Users can grant/deny consent specifically for Panxo +3. The CMP loads before Prebid.js to ensure consent is available + +Example TCF configuration with Prebid: + +```javascript +pbjs.setConfig({ + consentManagement: { + gdpr: { + cmpApi: 'iab', + timeout: 8000, + defaultGdprScope: true + }, + usp: { + cmpApi: 'iab', + timeout: 1000 + } + } +}); +``` + +# User Sync + +Panxo supports pixel-based user sync. Enable it in your Prebid configuration: + +```javascript +pbjs.setConfig({ + userSync: { + filterSettings: { + pixel: { + bidders: ['panxo'], + filter: 'include' + } + } + } +}); +``` + +# First Party Data + +This adapter supports First Party Data via the `ortb2` configuration: + +```javascript +pbjs.setConfig({ + ortb2: { + site: { + name: 'Example Site', + cat: ['IAB1'], + content: { + keywords: 'technology, ai' + } + }, + user: { + data: [{ + name: 'example-data-provider', + segment: [{ id: 'segment-1' }] + }] + } + } +}); +``` + +# Supply Chain (schain) + +Supply chain information is automatically passed when configured: + +```javascript +pbjs.setConfig({ + schain: { + validation: 'relaxed', + config: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'publisher-domain.com', + sid: '12345', + hp: 1 + }] + } + } +}); +``` + +# Floor Prices + +This adapter supports the Prebid Price Floors Module. Configure floors as needed: + +```javascript +pbjs.setConfig({ + floors: { + enforcement: { floorDeals: true }, + data: { + default: 0.50, + schema: { fields: ['mediaType'] }, + values: { 'banner': 0.50 } + } + } +}); +``` + +# Win Notifications + +This adapter automatically fires win notification URLs (nurl) when a bid wins the auction. No additional configuration is required. + +# Contact + +For support or questions: +- Email: tech@panxo.ai +- Documentation: https://docs.panxo.ai diff --git a/modules/panxoRtdProvider.js b/modules/panxoRtdProvider.js new file mode 100644 index 00000000000..5865106e564 --- /dev/null +++ b/modules/panxoRtdProvider.js @@ -0,0 +1,227 @@ +/** + * This module adds Panxo AI traffic classification to the real time data module. + * + * The {@link module:modules/realTimeData} module is required. + * The module injects the Panxo signal collection script, enriching bid requests + * with AI traffic classification data and contextual signals for improved targeting. + * @module modules/panxoRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { + prefixLog, + mergeDeep, + generateUUID, + getWindowSelf, +} from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + +const SUBMODULE_NAME = 'panxo'; +const SCRIPT_URL = 'https://api.idsequoia.ai/rtd.js'; + +const { logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); + +/** @type {string} */ +let siteId = ''; + +/** @type {boolean} */ +let verbose = false; + +/** @type {string} */ +let sessionId = ''; + +/** @type {Object} */ +let panxoData = {}; + +/** @type {boolean} */ +let implReady = false; + +/** @type {Array} */ +let pendingCallbacks = []; + +/** + * 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 + })); +} + +/** + * Validates configuration and loads the Panxo signal collection script. + * @param {SubmoduleConfig} config + */ +function load(config) { + siteId = config?.params?.siteId || ''; + if (!siteId || typeof siteId !== 'string') { + throw new Error(`The 'siteId' parameter is required and must be a string`); + } + + // siteId is a 16-character hex hash identifying the publisher property + if (!/^[a-f0-9]{16}$/.test(siteId)) { + throw new Error(`The 'siteId' parameter must be a valid 16-character hex identifier`); + } + + // Load/reset the state + verbose = !!config?.params?.verbose; + sessionId = generateUUID(); + panxoData = {}; + implReady = false; + pendingCallbacks = []; + + const refDomain = getRefererInfo().domain || ''; + + // The implementation script uses the session parameter to register + // a bridge API on window['panxo_' + sessionId] + const scriptUrl = `${SCRIPT_URL}?siteId=${siteId}&session=${sessionId}&r=${refDomain}`; + + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, onImplLoaded); +} + +/** + * Callback invoked when the external script finishes loading. + * Establishes the bridge between this RTD submodule and the implementation. + */ +function onImplLoaded() { + const wnd = getWindowSelf(); + const impl = wnd[`panxo_${sessionId}`]; + if (typeof impl !== 'object' || typeof impl.connect !== 'function') { + if (verbose) logWarn('onload', 'Unable to access the implementation script'); + if (!implReady) { + implReady = true; + flushPendingCallbacks(); + } + return; + } + + // Set up the bridge. The callback may be called multiple times as + // more precise signal data becomes available. + impl.connect(getGlobal(), onImplMessage); +} + +/** + * Bridge callback invoked by the implementation script to update signal data. + * When the first signal arrives, flushes any pending auction callbacks so + * the auction can proceed with enriched data. + * @param {Object} msg + */ +function onImplMessage(msg) { + if (!msg || typeof msg !== 'object') { + return; + } + + switch (msg.type) { + case 'signal': { + panxoData = mergeDeep({}, msg.data || {}); + if (!implReady) { + implReady = true; + flushPendingCallbacks(); + } + break; + } + case 'error': { + logError('impl', msg.data || ''); + if (!implReady) { + implReady = true; + flushPendingCallbacks(); + } + break; + } + } +} + +/** + * Flush all pending getBidRequestData callbacks. + * Called when the implementation script sends its first signal. + */ +function flushPendingCallbacks() { + const cbs = pendingCallbacks.splice(0); + cbs.forEach(cb => cb()); +} + +/** + * Called once per auction to enrich bid request ORTB data. + * + * If the implementation script has already sent signal data, enrichment + * happens synchronously and the callback fires immediately. Otherwise the + * callback is deferred until the first signal arrives. The Prebid RTD + * framework enforces `auctionDelay` as the upper bound on this wait, so + * the auction is never blocked indefinitely. + * + * Adds the following fields: + * - device.ext.panxo: session signal token for traffic verification + * - site.ext.data.panxo: contextual AI traffic classification data + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ +function onGetBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + function enrichAndDone() { + const ortb2 = {}; + + // Add device-level signal (opaque session token) + if (panxoData.device) { + mergeDeep(ortb2, { device: { ext: { panxo: panxoData.device } } }); + } + + // Add site-level contextual data (AI classification) + if (panxoData.site && Object.keys(panxoData.site).length > 0) { + mergeDeep(ortb2, { site: { ext: { data: { panxo: panxoData.site } } } }); + } + + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, ortb2); + callback(); + } + + // If data already arrived, proceed immediately + if (implReady) { + enrichAndDone(); + return; + } + + // Otherwise, wait for the implementation script to send its first signal. + // The auctionDelay configured by the publisher (e.g. 1500ms) acts as the + // maximum wait time -- Prebid will call our callback when it expires. + pendingCallbacks.push(enrichAndDone); +} + +/** + * Exporting local functions for testing purposes. + */ +export const __TEST__ = { + SUBMODULE_NAME, + SCRIPT_URL, + main, + load, + onImplLoaded, + onImplMessage, + onGetBidRequestData, + flushPendingCallbacks +}; + +main(); diff --git a/modules/panxoRtdProvider.md b/modules/panxoRtdProvider.md new file mode 100644 index 00000000000..bc9be90d152 --- /dev/null +++ b/modules/panxoRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Panxo RTD Provider +Module Type: RTD Provider +Maintainer: prebid@panxo.ai +``` + +# Description + +The Panxo RTD module enriches OpenRTB bid requests with real-time AI traffic classification signals. It detects visits originating from AI assistants and provides contextual data through `device.ext.panxo` and `site.ext.data.panxo`, enabling the Panxo Bid Adapter and other demand partners to apply differentiated bidding on AI-referred inventory. + +To use this module, contact [publishers@panxo.ai](mailto:publishers@panxo.ai) or sign up at [app.panxo.com](https://app.panxo.com) to receive your property identifier. + +# Build + +```bash +gulp build --modules=rtdModule,panxoRtdProvider,... +``` + +> `rtdModule` is required to use the Panxo RTD module. + +# Configuration + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 300, + dataProviders: [{ + name: 'panxo', + waitForIt: true, + params: { + siteId: 'a1b2c3d4e5f67890' + } + }] + } +}); +``` + +## Parameters + +| Name | Type | Description | Required | +| :-------- | :------ | :----------------------------------------------------- | :------- | +| `siteId` | String | 16-character hex property identifier provided by Panxo | Yes | +| `verbose` | Boolean | Enable verbose logging for troubleshooting | No | diff --git a/modules/permutiveIdentityManagerIdSystem.js b/modules/permutiveIdentityManagerIdSystem.js index f3849644445..cbd2a1b0d2b 100644 --- a/modules/permutiveIdentityManagerIdSystem.js +++ b/modules/permutiveIdentityManagerIdSystem.js @@ -1,7 +1,9 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js' import {submodule} from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js' -import {prefixLog, safeJSONParse} from '../src/utils.js' +import {deepAccess, prefixLog, safeJSONParse} from '../src/utils.js' +import {hasPurposeConsent} from '../libraries/permutiveUtils/index.js' +import {VENDORLESS_GVLID} from "../src/consentHandler.js"; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig @@ -10,14 +12,14 @@ import {prefixLog, safeJSONParse} from '../src/utils.js' */ const MODULE_NAME = 'permutiveIdentityManagerId' -const PERMUTIVE_GVLID = 361 const PERMUTIVE_ID_DATA_STORAGE_KEY = 'permutive-prebid-id' const ID5_DOMAIN = 'id5-sync.com' const LIVERAMP_DOMAIN = 'liveramp.com' const UID_DOMAIN = 'uidapi.com' +const GOOGLE_DOMAIN = 'google.com' -const PRIMARY_IDS = ['id5id', 'idl_env', 'uid2'] +const PRIMARY_IDS = ['id5id', 'idl_env', 'uid2', 'pairId'] export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}) @@ -81,7 +83,9 @@ export const permutiveIdentityManagerIdSubmodule = { * @type {string} */ name: MODULE_NAME, - gvlid: PERMUTIVE_GVLID, + gvlid: VENDORLESS_GVLID, + + disclosureURL: "https://assets.permutive.app/tcf/tcf.json", /** * decode the stored id value for passing to bid requests @@ -91,7 +95,19 @@ export const permutiveIdentityManagerIdSubmodule = { * @returns {(Object|undefined)} */ decode(value, config) { - return value + const storedPairId = value['pairId'] + let pairId + try { + if (storedPairId !== undefined) { + const decoded = safeJSONParse(atob(storedPairId)) + if (Array.isArray(decoded)) { + pairId = decoded + } + } + } catch (e) { + logger.logInfo('Error parsing pairId') + } + return pairId === undefined ? value : {...value, pairId} }, /** @@ -103,6 +119,12 @@ export const permutiveIdentityManagerIdSubmodule = { * @returns {IdResponse|undefined} */ getId(submoduleConfig, consentData, cacheIdObj) { + const enforceVendorConsent = deepAccess(submoduleConfig, 'params.enforceVendorConsent') + if (!hasPurposeConsent(consentData, [1], enforceVendorConsent)) { + logger.logInfo('GDPR purpose 1 consent not satisfied for Permutive Identity Manager') + return + } + const id = readFromSdkLocalStorage() if (Object.entries(id).length > 0) { logger.logInfo('found id in sdk storage') @@ -146,6 +168,10 @@ export const permutiveIdentityManagerIdSubmodule = { return data.ext } } + }, + 'pairId': { + source: GOOGLE_DOMAIN, + atype: 571187 } } } diff --git a/modules/permutiveIdentityManagerIdSystem.md b/modules/permutiveIdentityManagerIdSystem.md index ae249803d11..bb90e2c2dac 100644 --- a/modules/permutiveIdentityManagerIdSystem.md +++ b/modules/permutiveIdentityManagerIdSystem.md @@ -55,4 +55,10 @@ instead wait for up to the specified number of milliseconds for Permutive's SDK identities from the SDK directly if/when this happens. This value should be set to a value smaller than the `auctionDelay` set on the `userSync` configuration object, since -there is no point waiting longer than this as the auction will already have been triggered. \ No newline at end of file +there is no point waiting longer than this as the auction will already have been triggered. + +### enforceVendorConsent + +Publishers that require a vendor-based TCF check can set `enforceVendorConsent: true` in the module params. When enabled, +the module will only run when TCF vendor consent for Permutive (vendor 361) and purpose 1 is available. If the flag is +omitted or set to `false`, the module relies on the publisher-level purpose consent instead. \ No newline at end of file diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 886dc8b3b5f..972154e3933 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -9,6 +9,8 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {hasPurposeConsent} from '../libraries/permutiveUtils/index.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; @@ -17,7 +19,6 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; */ const MODULE_NAME = 'permutive' -const PERMUTIVE_GVLID = 361 const logger = prefixLog('[PermutiveRTD]') @@ -31,7 +32,9 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleNam function init(moduleConfig, userConsent) { readPermutiveModuleConfigFromCache() - return true + const enforceVendorConsent = deepAccess(moduleConfig, 'params.enforceVendorConsent') + + return hasPurposeConsent(userConsent, [1], enforceVendorConsent) } function liftIntoParams(params) { @@ -87,6 +90,7 @@ export function getModuleConfig(customModuleConfig) { maxSegs: 500, acBidders: [], overwrites: {}, + enforceVendorConsent: false, }, }, permutiveModuleConfig, @@ -467,7 +471,8 @@ let permutiveSDKInRealTime = false /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, - gvlid: PERMUTIVE_GVLID, + disclosureURL: "https://assets.permutive.app/tcf/tcf.json", + gvlid: VENDORLESS_GVLID, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { const completeBidRequestData = () => { logger.logInfo(`Request data updated`) diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 3cf3ed2b367..8ef632c75f5 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -47,37 +47,11 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd | params | Object | | - | | params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` | | params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | +| params.enforceVendorConsent | Boolean | When `true`, require TCF vendor consent for Permutive (vendor 361). See note below. | `false` | -#### Context +#### Consent -While Permutive is listed as a TCF vendor (ID: 361), Permutive does not obtain consent directly from the TCF. As we act as a processor on behalf of our publishers consent is given to the Permutive SDK by the publisher, not by the [GDPR Consent Management Module](https://prebid-docs.atre.net/dev-docs/modules/consentManagement.html). - -This means that if GDPR enforcement is configured within the Permutive SDK _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. - -If you are also using the [TCF Control Module](https://docs.prebid.org/dev-docs/modules/tcfControl.html), in order to prevent Permutive from being blocked, it needs to be labeled within the Vendor Exceptions. - -#### Instructions - -1. Publisher enables rules within Prebid.js configuration. -2. Label Permutive as an exception, as shown below. -```javascript -[ - { - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - vendorExceptions: ["permutive"] - }, - { - purpose: 'basicAds', - enforcePurpose: true, - enforceVendor: true, - vendorExceptions: [] - } -] -``` - -Before making any updates to this configuration, please ensure that this approach aligns with internal policies and current regulations regarding consent. +While Permutive is listed as a TCF vendor (ID: 361), Permutive does not typically obtain vendor consent from the TCF, but instead relies on the publisher purpose consents. Publishers wishing to use TCF vendor consent instead can add 361 to their CMP and set params.enforceVendorConsent to `true`. ## Cohort Activation with Permutive RTD Module diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 145f85956d7..d8cd4877b1d 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -1,16 +1,16 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {deepAccess, isArray, isNumber, isPlainObject} from '../src/utils.js'; -import {auctionManager} from '../src/auctionManager.js'; -import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {transformSizes} from '../libraries/sizeUtils/tranformSize.js'; -import {addUserId, hasUserInfo, getBidFloor} from '../libraries/adrelevantisUtils/bidderUtils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { deepAccess, isArray, isNumber, isPlainObject } from '../src/utils.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; +import { convertCamelToUnderscore } from '../libraries/appnexusUtils/anUtils.js'; +import { transformSizes } from '../libraries/sizeUtils/tranformSize.js'; +import { addUserId, hasUserInfo, getBidFloor } from '../libraries/adrelevantisUtils/bidderUtils.js'; const SOURCE = 'pbjs'; -const storageManager = getStorageManager({bidderCode: 'pixfuture'}); +const storageManager = getStorageManager({ bidderCode: 'pixfuture' }); const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; let pixID = ''; const GVLID = 839; @@ -31,7 +31,7 @@ export const spec = { isBidRequestValid(bid) { return !!(bid.sizes && bid.bidId && bid.params && - (bid.params.pix_id && (typeof bid.params.pix_id === 'string'))); + (bid.params.pix_id && (typeof bid.params.pix_id === 'string'))); }, buildRequests(validBidRequests, bidderRequest) { @@ -50,7 +50,7 @@ export const spec = { const userObjBid = ((validBidRequests) || []).find(hasUserInfo); let userObj = {}; if (config.getConfig('coppa') === true) { - userObj = {'coppa': true}; + userObj = { 'coppa': true }; } if (userObjBid) { @@ -62,7 +62,7 @@ export const spec = { const segs = []; userObjBid.params.user[param].forEach(val => { if (isNumber(val)) { - segs.push({'id': val}); + segs.push({ 'id': val }); } else if (isPlainObject(val)) { segs.push(val); } @@ -101,16 +101,17 @@ export const spec = { payload.referrer_detection = refererinfo; } - if (validBidRequests[0].userId) { + if (validBidRequests[0]?.ortb2?.user?.ext?.eids?.length) { const eids = []; + const ortbEids = validBidRequests[0].ortb2.user.ext.eids; - addUserId(eids, deepAccess(validBidRequests[0], `userId.criteoId`), 'criteo.com', null); - addUserId(eids, deepAccess(validBidRequests[0], `userId.unifiedId`), 'thetradedesk.com', null); - addUserId(eids, deepAccess(validBidRequests[0], `userId.id5Id`), 'id5.io', null); - addUserId(eids, deepAccess(validBidRequests[0], `userId.sharedId`), 'thetradedesk.com', null); - addUserId(eids, deepAccess(validBidRequests[0], `userId.identityLink`), 'liveramp.com', null); - addUserId(eids, deepAccess(validBidRequests[0], `userId.liveIntentId`), 'liveintent.com', null); - addUserId(eids, deepAccess(validBidRequests[0], `userId.fabrickId`), 'home.neustar', null); + ortbEids.forEach(eid => { + const source = eid.source; + const uids = eid.uids || []; + uids.forEach(uidObj => { + addUserId(eids, uidObj.id, source, uidObj.atype || null); + }); + }); if (eids.length) { payload.eids = eids; @@ -124,7 +125,7 @@ export const spec = { const ret = { url: `${hostname}/pixservices`, method: 'POST', - options: {withCredentials: true}, + options: { withCredentials: true }, data: { v: 'v' + '$prebid.version$', pageUrl: referer, @@ -245,7 +246,7 @@ function bidToTag(bid) { tag.reserve = bidFloor; } if (bid.params.position) { - tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; } else { const mediaTypePos = deepAccess(bid, `mediaTypes.banner.pos`) || deepAccess(bid, `mediaTypes.video.pos`); // only support unknown, atf, and btf values for position at this time @@ -283,7 +284,7 @@ function bidToTag(bid) { } if (bid.renderer) { - tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); + tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); } if (bid.params.frameworks && isArray(bid.params.frameworks)) { diff --git a/modules/priceFloors.ts b/modules/priceFloors.ts index 37149ee3a79..1ea98a337dd 100644 --- a/modules/priceFloors.ts +++ b/modules/priceFloors.ts @@ -448,7 +448,8 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { const noFloorSignalBiddersArray = getNoFloorSignalBidersArray(floorData) adUnits.forEach((adUnit) => { - adUnit.bids.forEach(bid => { + // adUnit.bids can be undefined + adUnit.bids?.forEach(bid => { // check if the bidder is in the no signal list const isNoFloorSignaled = noFloorSignalBiddersArray.some(bidderName => bidderName === bid.bidder) if (floorData.skipped || isNoFloorSignaled) { @@ -842,6 +843,11 @@ export type FloorsConfig = Pick * If set to false, the Price Floors Module will still provide floors for bid adapters, there will be no floor enforcement. */ enforceJS?: boolean; + /** + * Array of bidders to enforce JS floors on when enforceJS is true. + * Defaults to ['*'] (all bidders). + */ + enforceBidders?: (BidderCode | '*')[]; /** * If set to true (the default), the Price Floors Module will signal to Prebid Server to pass floors to it’s bid * adapters and enforce floors. @@ -900,6 +906,7 @@ export function handleSetFloorsConfig(config) { 'userIds', validateUserIdsConfig, 'enforcement', enforcement => pick(enforcement || {}, [ 'enforceJS', enforceJS => enforceJS !== false, // defaults to true + 'enforceBidders', enforceBidders => Array.isArray(enforceBidders) && enforceBidders.length > 0 ? enforceBidders : ['*'], 'enforcePBS', enforcePBS => enforcePBS === true, // defaults to false 'floorDeals', floorDeals => floorDeals === true, // defaults to false 'bidAdjustment', bidAdjustment => bidAdjustment !== false, // defaults to true, @@ -982,9 +989,12 @@ function addFloorDataToBid(floorData, floorInfo, bid: Partial, adjustedCpm) */ function shouldFloorBid(floorData, floorInfo, bid) { const enforceJS = deepAccess(floorData, 'enforcement.enforceJS') !== false; + const enforceBidders = deepAccess(floorData, 'enforcement.enforceBidders') || ['*']; + const bidderCode = bid?.adapterCode || bid?.bidderCode || bid?.bidder; + const shouldEnforceBidder = enforceBidders.includes('*') || (bidderCode != null && enforceBidders.includes(bidderCode)); const shouldFloorDeal = deepAccess(floorData, 'enforcement.floorDeals') === true || !bid.dealId; const bidBelowFloor = bid.floorData.cpmAfterAdjustments < floorInfo.matchingFloor; - return enforceJS && (bidBelowFloor && shouldFloorDeal); + return enforceJS && shouldEnforceBidder && (bidBelowFloor && shouldFloorDeal); } /** diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 5c66be10804..cea18be596f 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -1,185 +1,160 @@ -import { isFn, isPlainObject } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {deepSetValue} from '../src/utils.js'; const BIDDER_CODE = 'proxistore'; const PROXISTORE_VENDOR_ID = 418; -const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/prebid/multi'; -const COOKIE_LESS_URL = - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi'; - -function _createServerRequest(bidRequests, bidderRequest) { - var sizeIds = []; - bidRequests.forEach(function (bid) { - var sizeId = { - id: bid.bidId, - sizes: bid.sizes.map(function (size) { - return { - width: size[0], - height: size[1], - }; - }), - floor: _assignFloor(bid), - segments: _assignSegments(bid), - }; - sizeIds.push(sizeId); - }); - var payload = { - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bidRequests[0].auctionId, - transactionId: bidRequests[0].ortb2Imp?.ext?.tid, - bids: sizeIds, - website: bidRequests[0].params.website, - language: bidRequests[0].params.language, - gdpr: { - applies: false, - consentGiven: false, - }, - }; +const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/openrtb'; +const COOKIE_LESS_URL = 'https://abs.cookieless-proxistore.com/v3/rtb/openrtb'; +const SYNC_BASE_URL = 'https://abs.proxistore.com/v3/rtb/sync'; + +const converter = ortbConverter({ + context: { + mediaType: BANNER, + netRevenue: true, + ttl: 30, + currency: 'EUR', + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bidRequests = context.bidRequests; + if (bidRequests && bidRequests.length > 0) { + const params = bidRequests[0].params; + if (params.website) { + deepSetValue(request, 'ext.proxistore.website', params.website); + } + if (params.language) { + deepSetValue(request, 'ext.proxistore.language', params.language); + } + } + return request; + } +}); - if (bidderRequest && bidderRequest.gdprConsent) { - var gdprConsent = bidderRequest.gdprConsent; +/** + * Determines whether or not the given bid request is valid. + * + * @param bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(bid) { + return !!(bid.params.website && bid.params.language); +} - if ( - typeof gdprConsent.gdprApplies === 'boolean' && - gdprConsent.gdprApplies - ) { - payload.gdpr.applies = true; - } +/** + * Make a server request from the list of BidRequests. + * + * @param bidRequests - an array of bids + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests(bidRequests, bidderRequest) { + let gdprApplies = false; + let consentGiven = false; + + if (bidderRequest && bidderRequest.gdprConsent) { + const gdprConsent = bidderRequest.gdprConsent; - if ( - typeof gdprConsent.consentString === 'string' && - gdprConsent.consentString - ) { - payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; + if (typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + gdprApplies = true; } if (gdprConsent.vendorData) { - var vendorData = gdprConsent.vendorData; - + const vendorData = gdprConsent.vendorData; if ( vendorData.vendor && vendorData.vendor.consents && - typeof vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== - 'undefined' + vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== 'undefined' ) { - payload.gdpr.consentGiven = - !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; + consentGiven = !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; } } } - var options = { + const options = { contentType: 'application/json', - withCredentials: payload.gdpr.consentGiven, + withCredentials: consentGiven, customHeaders: { - version: '1.0.4', + version: '2.0.0', }, }; - var endPointUri = - payload.gdpr.consentGiven || !payload.gdpr.applies - ? COOKIE_BASE_URL - : COOKIE_LESS_URL; + + const endPointUri = consentGiven || !gdprApplies ? COOKIE_BASE_URL : COOKIE_LESS_URL; return { method: 'POST', url: endPointUri, - data: JSON.stringify(payload), + data: converter.toORTB({ bidRequests, bidderRequest }), options: options, }; } -function _assignSegments(bid) { - var segs = (bid.ortb2 && bid.ortb2.user && bid.ortb2.user.ext && bid.ortb2.user.ext.data && bid.ortb2.user.ext.data.sd_rtd && bid.ortb2.user.ext.data.sd_rtd.segments ? bid.ortb2.user.ext.data.sd_rtd.segments : []); - var cats = {}; - if (bid.ortb2 && bid.ortb2.site && bid.ortb2.site.ext && bid.ortb2.site.ext.data && bid.ortb2.site.ext.data.sd_rtd) { - if (bid.ortb2.site.ext.data.sd_rtd.categories) { - segs = segs.concat(bid.ortb2.site.ext.data.sd_rtd.categories); - } - if (bid.ortb2.site.ext.data.sd_rtd.categories_score) { - cats = bid.ortb2.site.ext.data.sd_rtd.categories_score; - } - } - - return { - segments: segs, - contextual_categories: cats - }; -} - -function _createBidResponse(response) { - return { - requestId: response.requestId, - cpm: response.cpm, - width: response.width, - height: response.height, - ad: response.ad, - ttl: response.ttl, - creativeId: response.creativeId, - currency: response.currency, - netRevenue: response.netRevenue, - vastUrl: response.vastUrl, - vastXml: response.vastXml, - dealId: response.dealId, - meta: response.meta, - }; -} /** - * Determines whether or not the given bid request is valid. + * Unpack the response from the server into a list of bids. * - * @param bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. + * @param response + * @param request + * @return An array of bids which were nested inside the server. */ - -function isBidRequestValid(bid) { - return !!(bid.params.website && bid.params.language); +function interpretResponse(response, request) { + if (response.body) { + return converter.fromORTB({response: response.body, request: request.data}).bids; + } + return []; } -/** - * Make a server request from the list of BidRequests. - * - * @param bidRequests - an array of bids - * @param bidderRequest - * @return ServerRequest Info describing the request to the server. - */ - -function buildRequests(bidRequests, bidderRequest) { - var request = _createServerRequest(bidRequests, bidderRequest); - return request; -} /** - * Unpack the response from the server into a list of bids. + * Register user sync pixels and iframes. * - * @param serverResponse A successful response from the server. - * @param bidRequest Request original server request - * @return An array of bids which were nested inside the server. + * @param syncOptions - which sync types are enabled + * @param responses - server responses + * @param gdprConsent - GDPR consent data + * @return Array of sync objects */ +function getUserSyncs(syncOptions, responses, gdprConsent) { + const syncs = []; -function interpretResponse(serverResponse, bidRequest) { - return serverResponse.body.map(_createBidResponse); -} + // Only sync if consent given or GDPR doesn't apply + const consentGiven = gdprConsent?.vendorData?.vendor?.consents?.[PROXISTORE_VENDOR_ID]; + if (gdprConsent?.gdprApplies && !consentGiven) { + return syncs; + } -function _assignFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.bidFloor ? bid.params.bidFloor : null; + const params = new URLSearchParams(); + if (gdprConsent) { + params.set('gdpr', gdprConsent.gdprApplies ? '1' : '0'); + if (gdprConsent.consentString) { + params.set('gdpr_consent', gdprConsent.consentString); + } + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: `${SYNC_BASE_URL}/image?${params}` + }); } - const floor = bid.getFloor({ - currency: 'EUR', - mediaType: 'banner', - size: '*', - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { - return floor.floor; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `${SYNC_BASE_URL}/iframe?${params}` + }); } - return null; + + return syncs; } export const spec = { code: BIDDER_CODE, + gvlid: PROXISTORE_VENDOR_ID, isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, interpretResponse: interpretResponse, - gvlid: PROXISTORE_VENDOR_ID, + getUserSyncs: getUserSyncs, + supportedMediaTypes: [BANNER], + browsingTopics: true, }; registerBidder(spec); diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 38e27e8b7b9..19b1c11ffe9 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { isArray, logError, logWarn, pick } from '../src/utils.js'; +import { isArray, logError, logWarn, pick, isFn } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { BID_STATUS, STATUS, REJECTION_REASON } from '../src/constants.js'; @@ -233,6 +233,7 @@ function getFeatureLevelDetails(auctionCache) { function getListOfIdentityPartners() { const namespace = getGlobal(); + if (!isFn(namespace.getUserIds)) return; const publisherProvidedEids = namespace.getConfig("ortb2.user.eids") || []; const availableUserIds = namespace.getUserIds() || {}; const identityModules = namespace.getConfig('userSync')?.userIds || []; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 7f2abeb5b6a..5051eb8e6ee 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -8,6 +8,7 @@ import { bidderSettings } from '../src/bidderSettings.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS } from '../src/constants.js'; import { addDealCustomTargetings, addPMPDeals } from '../libraries/dealUtils/dealUtils.js'; +import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -98,6 +99,9 @@ const converter = ortbConverter({ if (pmzoneid) imp.ext.pmZoneId = pmzoneid; setImpTagId(imp, adSlot.trim(), hashedKey); setImpFields(imp); + imp.ext?.ae != null && delete imp.ext.ae; + imp.ext?.igs != null && delete imp.ext.igs; + imp.ext?.paapi != null && delete imp.ext.paapi; // check for battr data types ['banner', 'video', 'native'].forEach(key => { if (imp[key]?.battr && !Array.isArray(imp[key].battr)) { @@ -563,12 +567,6 @@ const validateBlockedCategories = (bcats) => { return [...new Set(bcats.filter(item => typeof item === 'string' && item.length >= 3))]; } -const getConnectionType = () => { - const connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection); - const types = { ethernet: 1, wifi: 2, 'slow-2g': 4, '2g': 4, '3g': 5, '4g': 6 }; - return types[connection?.effectiveType] || 0; -} - /** * Optimizes the impressions array by consolidating impressions for the same ad unit and media type * @param {Array} imps - Array of impression objects @@ -758,6 +756,7 @@ export const spec = { code: BIDDER_CODE, gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], + alwaysHasCapacity: true, /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * @@ -856,16 +855,6 @@ export const spec = { */ interpretResponse: (response, request) => { const { bids } = converter.fromORTB({ response: response.body, request: request.data }); - const fledgeAuctionConfigs = deepAccess(response.body, 'ext.fledge_auction_configs'); - if (fledgeAuctionConfigs) { - return { - bids, - paapi: Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => ({ - bidId, - config: { auctionSignals: {}, ...cfg } - })) - }; - } return bids; }, diff --git a/modules/pubmaticRtdProvider.js b/modules/pubmaticRtdProvider.js index b373ac0a545..6dbb3a28aac 100644 --- a/modules/pubmaticRtdProvider.js +++ b/modules/pubmaticRtdProvider.js @@ -1,488 +1,103 @@ import { submodule } from '../src/hook.js'; -import { logError, logInfo, isPlainObject, isEmpty, isFn, mergeDeep } from '../src/utils.js'; -import { config as conf } from '../src/config.js'; -import { getDeviceType as fetchDeviceType, getOS } from '../libraries/userAgentUtils/index.js'; -import { getLowEntropySUA } from '../src/fpd/sua.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { REJECTION_REASON } from '../src/constants.js'; +import { logError, mergeDeep, isPlainObject, isEmpty } from '../src/utils.js'; + +import { PluginManager } from '../libraries/pubmaticUtils/plugins/pluginManager.js'; +import { FloorProvider } from '../libraries/pubmaticUtils/plugins/floorProvider.js'; +import { UnifiedPricingRule } from '../libraries/pubmaticUtils/plugins/unifiedPricingRule.js'; +import { DynamicTimeout } from '../libraries/pubmaticUtils/plugins/dynamicTimeout.js'; /** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('./rtdModule/index.js').RtdSubmodule} RtdSubmodule */ - /** * This RTD module has a dependency on the priceFloors module. * We utilize the continueAuction function from the priceFloors module to incorporate price floors data into the current auction. */ -import { continueAuction } from './priceFloors.js'; // eslint-disable-line prebid/validate-imports export const CONSTANTS = Object.freeze({ SUBMODULE_NAME: 'pubmatic', REAL_TIME_MODULE: 'realTimeData', LOG_PRE_FIX: 'PubMatic-Rtd-Provider: ', - UTM: 'utm_', - UTM_VALUES: { - TRUE: '1', - FALSE: '0' - }, - TIME_OF_DAY_VALUES: { - MORNING: 'morning', - AFTERNOON: 'afternoon', - EVENING: 'evening', - NIGHT: 'night', - }, ENDPOINTS: { BASEURL: 'https://ads.pubmatic.com/AdServer/js/pwt', - FLOORS: 'floors.json', CONFIGS: 'config.json' - }, - BID_STATUS: { - NOBID: 0, - WON: 1, - FLOORED: 2 - }, - MULTIPLIERS: { - WIN: 1.0, - FLOORED: 0.8, - NOBID: 1.2 - }, - TARGETING_KEYS: { - PM_YM_FLRS: 'pm_ym_flrs', // Whether RTD floor was applied - PM_YM_FLRV: 'pm_ym_flrv', // Final floor value (after applying multiplier) - PM_YM_BID_S: 'pm_ym_bid_s' // Bid status (0: No bid, 1: Won, 2: Floored) } }); -const BROWSER_REGEX_MAP = [ - { regex: /\b(?:crios)\/([\w.]+)/i, id: 1 }, // Chrome for iOS - { regex: /(edg|edge)(?:e|ios|a)?(?:\/([\w.]+))?/i, id: 2 }, // Edge - { regex: /(opera|opr)(?:.+version\/|[/ ]+)([\w.]+)/i, id: 3 }, // Opera - { regex: /(?:ms|\()(ie) ([\w.]+)|(?:trident\/[\w.]+)/i, id: 4 }, // Internet Explorer - { regex: /fxios\/([-\w.]+)/i, id: 5 }, // Firefox for iOS - { regex: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w.]+);)/i, id: 6 }, // Facebook In-App Browser - { regex: / wv\).+(chrome)\/([\w.]+)/i, id: 7 }, // Chrome WebView - { regex: /droid.+ version\/([\w.]+)\b.+(?:mobile safari|safari)/i, id: 8 }, // Android Browser - { regex: /(chrome|crios)(?:\/v?([\w.]+))?\b/i, id: 9 }, // Chrome - { regex: /version\/([\w.,]+) .*mobile\/\w+ (safari)/i, id: 10 }, // Safari Mobile - { regex: /version\/([\w(.|,]+) .*(mobile ?safari|safari)/i, id: 11 }, // Safari - { regex: /(firefox)\/([\w.]+)/i, id: 12 } // Firefox -]; - -export const defaultValueTemplate = { - currency: 'USD', - skipRate: 0, - schema: { - fields: ['mediaType', 'size'] - } -}; - -let initTime; -let _fetchFloorRulesPromise = null; let _fetchConfigPromise = null; -export let configMerged; -// configMerged is a reference to the function that can resolve configMergedPromise whenever we want -let configMergedPromise = new Promise((resolve) => { configMerged = resolve; }); -export let _country; -// Store multipliers from floors.json, will use default values from CONSTANTS if not available -export let _multipliers = null; - -// Use a private variable for profile configs -let _profileConfigs; -// Export getter and setter functions for _profileConfigs -export const getProfileConfigs = () => _profileConfigs; -export const setProfileConfigs = (configs) => { _profileConfigs = configs; }; - -// Waits for a given promise to resolve within a timeout -export function withTimeout(promise, ms) { - let timeout; - const timeoutPromise = new Promise((resolve) => { - timeout = setTimeout(() => resolve(undefined), ms); - }); - - return Promise.race([promise.finally(() => clearTimeout(timeout)), timeoutPromise]); -} - -// Utility Functions -export const getCurrentTimeOfDay = () => { - const currentHour = new Date().getHours(); - - return currentHour < 5 ? CONSTANTS.TIME_OF_DAY_VALUES.NIGHT - : currentHour < 12 ? CONSTANTS.TIME_OF_DAY_VALUES.MORNING - : currentHour < 17 ? CONSTANTS.TIME_OF_DAY_VALUES.AFTERNOON - : currentHour < 19 ? CONSTANTS.TIME_OF_DAY_VALUES.EVENING - : CONSTANTS.TIME_OF_DAY_VALUES.NIGHT; -} - -export const getBrowserType = () => { - const brandName = getLowEntropySUA()?.browsers - ?.map(b => b.brand.toLowerCase()) - .join(' ') || ''; - const browserMatch = brandName ? BROWSER_REGEX_MAP.find(({ regex }) => regex.test(brandName)) : -1; - - if (browserMatch?.id) return browserMatch.id.toString(); - - const userAgent = navigator?.userAgent; - let browserIndex = userAgent == null ? -1 : 0; - - if (userAgent) { - browserIndex = BROWSER_REGEX_MAP.find(({ regex }) => regex.test(userAgent))?.id || 0; - } - return browserIndex.toString(); -} - -// Find all bids for a specific ad unit -function findBidsForAdUnit(auction, code) { - return auction?.bidsReceived?.filter(bid => bid.adUnitCode === code) || []; -} +let _ymConfigPromise; +export const getYmConfigPromise = () => _ymConfigPromise; +export const setYmConfigPromise = (promise) => { _ymConfigPromise = promise; }; -// Find rejected bids for a specific ad unit -function findRejectedBidsForAdUnit(auction, code) { - if (!auction?.bidsRejected) return []; +export function ConfigJsonManager() { + let _ymConfig = {}; + const getYMConfig = () => _ymConfig; + const setYMConfig = (config) => { _ymConfig = config; } + let country; - // If bidsRejected is an array - if (Array.isArray(auction.bidsRejected)) { - return auction.bidsRejected.filter(bid => bid.adUnitCode === code); - } + /** + * Fetch configuration from the server + * @param {string} publisherId - Publisher ID + * @param {string} profileId - Profile ID + * @returns {Promise} - Promise resolving to the config object + */ + async function fetchConfig(publisherId, profileId) { + try { + const url = `${CONSTANTS.ENDPOINTS.BASEURL}/${publisherId}/${profileId}/${CONSTANTS.ENDPOINTS.CONFIGS}`; + const response = await fetch(url); + + if (!response.ok) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching config: Not ok`); + return null; + } - // If bidsRejected is an object mapping bidders to their rejected bids - if (typeof auction.bidsRejected === 'object') { - return Object.values(auction.bidsRejected) - .filter(Array.isArray) - .flatMap(bidderBids => bidderBids.filter(bid => bid.adUnitCode === code)); - } + // Extract country code if available + const cc = response.headers?.get('country_code'); + country = cc ? cc.split(',')?.map(code => code.trim())[0] : undefined; - return []; -} + // Parse the JSON response + const ymConfigs = await response.json(); -// Find a rejected bid due to price floor -function findRejectedFloorBid(rejectedBids) { - return rejectedBids.find(bid => { - return bid.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET && - (bid.floorData?.floorValue && bid.cpm < bid.floorData.floorValue); - }); -} + if (!isPlainObject(ymConfigs) || isEmpty(ymConfigs)) { + logError(`${CONSTANTS.LOG_PRE_FIX} profileConfigs is not an object or is empty`); + return null; + } -// Find the winning or highest bid for an ad unit -function findWinningBid(adUnitCode) { - try { - const pbjs = getGlobal(); - if (!pbjs?.getHighestCpmBids) return null; + // Store the configuration + setYMConfig(ymConfigs); - const highestCpmBids = pbjs.getHighestCpmBids(adUnitCode); - if (!highestCpmBids?.length) { - logInfo(CONSTANTS.LOG_PRE_FIX, `No highest CPM bids found for ad unit: ${adUnitCode}`); + return true; + } catch (error) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching config: ${error}`); return null; } - - const highestCpmBid = highestCpmBids[0]; - logInfo(CONSTANTS.LOG_PRE_FIX, `Found highest CPM bid using pbjs.getHighestCpmBids() for ad unit: ${adUnitCode}, CPM: ${highestCpmBid.cpm}`); - return highestCpmBid; - } catch (error) { - logError(CONSTANTS.LOG_PRE_FIX, `Error finding highest CPM bid: ${error}`); - return null; - } -} - -// Find floor value from bidder requests -function findFloorValueFromBidderRequests(auction, code) { - if (!auction?.bidderRequests?.length) return 0; - - // Find all bids in bidder requests for this ad unit - const bidsFromRequests = auction.bidderRequests - .flatMap(request => request.bids || []) - .filter(bid => bid.adUnitCode === code); - - if (!bidsFromRequests.length) { - logInfo(CONSTANTS.LOG_PRE_FIX, `No bids found for ad unit: ${code}`); - return 0; } - const bidWithGetFloor = bidsFromRequests.find(bid => bid.getFloor); - if (!bidWithGetFloor) { - logInfo(CONSTANTS.LOG_PRE_FIX, `No bid with getFloor method found for ad unit: ${code}`); - return 0; - } - - // Helper function to extract sizes with their media types from a source object - const extractSizes = (source) => { - if (!source) return null; - - const result = []; - - // Extract banner sizes - if (source.mediaTypes?.banner?.sizes) { - source.mediaTypes.banner.sizes.forEach(size => { - result.push({ - size, - mediaType: 'banner' - }); - }); - } - - // Extract video sizes - if (source.mediaTypes?.video?.playerSize) { - const playerSize = source.mediaTypes.video.playerSize; - // Handle both formats: [[w, h]] and [w, h] - const videoSizes = Array.isArray(playerSize[0]) ? playerSize : [playerSize]; - - videoSizes.forEach(size => { - result.push({ - size, - mediaType: 'video' - }); - }); - } - - // Use general sizes as fallback if no specific media types found - if (result.length === 0 && source.sizes) { - source.sizes.forEach(size => { - result.push({ - size, - mediaType: 'banner' // Default to banner for general sizes - }); - }); - } - - return result.length > 0 ? result : null; - }; - - // Try to get sizes from different sources in order of preference - const adUnit = auction.adUnits?.find(unit => unit.code === code); - let sizes = extractSizes(adUnit) || extractSizes(bidWithGetFloor); - - // Handle fallback to wildcard size if no sizes found - if (!sizes) { - sizes = [{ size: ['*', '*'], mediaType: 'banner' }]; - logInfo(CONSTANTS.LOG_PRE_FIX, `No sizes found, using wildcard size for ad unit: ${code}`); - } - - // Try to get floor values for each size - let minFloor = -1; - - for (const sizeObj of sizes) { - // Extract size and mediaType from the object - const { size, mediaType } = sizeObj; - - // Call getFloor with the appropriate media type - const floorInfo = bidWithGetFloor.getFloor({ - currency: 'USD', // Default currency - mediaType: mediaType, // Use the media type we extracted - size: size - }); - - if (floorInfo?.floor && !isNaN(parseFloat(floorInfo.floor))) { - const floorValue = parseFloat(floorInfo.floor); - logInfo(CONSTANTS.LOG_PRE_FIX, `Floor value for ${mediaType} size ${size}: ${floorValue}`); - - // Update minimum floor value - minFloor = minFloor === -1 ? floorValue : Math.min(minFloor, floorValue); - } - } - - if (minFloor !== -1) { - logInfo(CONSTANTS.LOG_PRE_FIX, `Calculated minimum floor value ${minFloor} for ad unit: ${code}`); - return minFloor; - } - - logInfo(CONSTANTS.LOG_PRE_FIX, `No floor data found for ad unit: ${code}`); - return 0; -} - -// Select multiplier based on priority order: floors.json → config.json → default -function selectMultiplier(multiplierKey, profileConfigs) { - // Define sources in priority order - const multiplierSources = [ - { - name: 'config.json', - getValue: () => { - const configPath = profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.multiplier; - const lowerKey = multiplierKey.toLowerCase(); - return configPath && lowerKey in configPath ? configPath[lowerKey] : null; - } - }, - { - name: 'floor.json', - getValue: () => _multipliers && multiplierKey in _multipliers ? _multipliers[multiplierKey] : null - }, - { - name: 'default', - getValue: () => CONSTANTS.MULTIPLIERS[multiplierKey] - } - ]; - - // Find the first source with a non-null value - for (const source of multiplierSources) { - const value = source.getValue(); - if (value != null) { - return { value, source: source.name }; - } + /** + * Get configuration by name + * @param {string} name - Plugin name + * @returns {Object} - Plugin configuration + */ + const getConfigByName = (name) => { + return getYMConfig()?.plugins?.[name]; } - // Fallback (shouldn't happen due to default source) - return { value: CONSTANTS.MULTIPLIERS[multiplierKey], source: 'default' }; -} - -// Identify winning bid scenario and return scenario data -function handleWinningBidScenario(winningBid, code) { return { - scenario: 'winning', - bidStatus: CONSTANTS.BID_STATUS.WON, - baseValue: winningBid.cpm, - multiplierKey: 'WIN', - logMessage: `Bid won for ad unit: ${code}, CPM: ${winningBid.cpm}` + fetchConfig, + getYMConfig, + setYMConfig, + getConfigByName, + get country() { return country; } }; } -// Identify rejected floor bid scenario and return scenario data -function handleRejectedFloorBidScenario(rejectedFloorBid, code) { - const baseValue = rejectedFloorBid.floorData?.floorValue || 0; - return { - scenario: 'rejected', - bidStatus: CONSTANTS.BID_STATUS.FLOORED, - baseValue, - multiplierKey: 'FLOORED', - logMessage: `Bid rejected due to price floor for ad unit: ${code}, Floor value: ${baseValue}, Bid CPM: ${rejectedFloorBid.cpm}` - }; -} - -// Identify no bid scenario and return scenario data -function handleNoBidScenario(auction, code) { - const baseValue = findFloorValueFromBidderRequests(auction, code); - return { - scenario: 'nobid', - bidStatus: CONSTANTS.BID_STATUS.NOBID, - baseValue, - multiplierKey: 'NOBID', - logMessage: `No bids for ad unit: ${code}, Floor value: ${baseValue}` - }; -} - -// Determine which scenario applies based on bid conditions -function determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) { - return winningBid ? handleWinningBidScenario(winningBid, code) - : rejectedFloorBid ? handleRejectedFloorBidScenario(rejectedFloorBid, code) - : handleNoBidScenario(auction, code); -} - -// Main function that determines bid status and calculates values -function determineBidStatusAndValues(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code) { - const profileConfigs = getProfileConfigs(); - - // Determine the scenario based on bid conditions - const { bidStatus, baseValue, multiplierKey, logMessage } = - determineScenario(winningBid, rejectedFloorBid, bidsForAdUnit, auction, code); - - // Select the appropriate multiplier - const { value: multiplier, source } = selectMultiplier(multiplierKey, profileConfigs); - logInfo(CONSTANTS.LOG_PRE_FIX, logMessage + ` (Using ${source} multiplier: ${multiplier})`); - - return { bidStatus, baseValue, multiplier }; -} - -// Getter Functions -export const getOs = () => getOS().toString(); -export const getDeviceType = () => fetchDeviceType().toString(); -export const getCountry = () => _country; -export const getBidder = (request) => request?.bidder; -export const getUtm = () => { - const url = new URL(window.location?.href); - const urlParams = new URLSearchParams(url?.search); - return urlParams && urlParams.toString().includes(CONSTANTS.UTM) ? CONSTANTS.UTM_VALUES.TRUE : CONSTANTS.UTM_VALUES.FALSE; -} - -export const getFloorsConfig = (floorsData, profileConfigs) => { - if (!isPlainObject(profileConfigs) || isEmpty(profileConfigs)) { - logError(`${CONSTANTS.LOG_PRE_FIX} profileConfigs is not an object or is empty`); - return undefined; - } - - // Floor configs from adunit / setconfig - const defaultFloorConfig = conf.getConfig('floors') ?? {}; - if (defaultFloorConfig?.endpoint) { - delete defaultFloorConfig.endpoint; - } - // Plugin data from profile - const dynamicFloors = profileConfigs?.plugins?.dynamicFloors; - - // If plugin disabled or config not present, return undefined - if (!dynamicFloors?.enabled || !dynamicFloors?.config) { - return undefined; - } - - const config = { ...dynamicFloors.config }; - - // default values provided by publisher on profile - const defaultValues = config.defaultValues ?? {}; - // If floorsData is not present, use default values - const finalFloorsData = floorsData ?? { ...defaultValueTemplate, values: { ...defaultValues } }; - - delete config.defaultValues; - // If skiprate is provided in configs, overwrite the value in finalFloorsData - (config.skipRate !== undefined) && (finalFloorsData.skipRate = config.skipRate); - - // merge default configs from page, configs - return { - floors: { - ...defaultFloorConfig, - ...config, - data: finalFloorsData, - additionalSchemaFields: { - deviceType: getDeviceType, - timeOfDay: getCurrentTimeOfDay, - browser: getBrowserType, - os: getOs, - utm: getUtm, - country: getCountry, - bidder: getBidder, - }, - }, - }; -}; - -export const fetchData = async (publisherId, profileId, type) => { - try { - const endpoint = CONSTANTS.ENDPOINTS[type]; - const baseURL = (type === 'FLOORS') ? `${CONSTANTS.ENDPOINTS.BASEURL}/floors` : CONSTANTS.ENDPOINTS.BASEURL; - const url = `${baseURL}/${publisherId}/${profileId}/${endpoint}`; - const response = await fetch(url); - - if (!response.ok) { - logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching ${type}: Not ok`); - return; - } - - if (type === "FLOORS") { - const cc = response.headers?.get('country_code'); - _country = cc ? cc.split(',')?.map(code => code.trim())[0] : undefined; - } - - const data = await response.json(); - - // Extract multipliers from floors.json if available - if (type === "FLOORS" && data && data.multiplier) { - // Map of source keys to destination keys - const multiplierKeys = { - 'win': 'WIN', - 'floored': 'FLOORED', - 'nobid': 'NOBID' - }; +// Create core components +export const pluginManager = PluginManager(); +export const configJsonManager = ConfigJsonManager(); - // Initialize _multipliers and only add keys that exist in data.multiplier - _multipliers = Object.entries(multiplierKeys) - .reduce((acc, [srcKey, destKey]) => { - if (srcKey in data.multiplier) { - acc[destKey] = data.multiplier[srcKey]; - } - return acc; - }, {}); - - logInfo(CONSTANTS.LOG_PRE_FIX, `Using multipliers from floors.json: ${JSON.stringify(_multipliers)}`); - } - - return data; - } catch (error) { - logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching ${type}: ${error}`); - } -}; +// Register plugins +pluginManager.register('dynamicFloors', FloorProvider); +pluginManager.register('unifiedPricingRule', UnifiedPricingRule); +pluginManager.register('dynamicTimeout', DynamicTimeout); /** * Initialize the Pubmatic RTD Module. @@ -491,7 +106,6 @@ export const fetchData = async (publisherId, profileId, type) => { * @returns {boolean} */ const init = (config, _userConsent) => { - initTime = Date.now(); // Capture the initialization time let { publisherId, profileId } = config?.params || {}; if (!publisherId || !profileId) { @@ -502,30 +116,14 @@ const init = (config, _userConsent) => { publisherId = String(publisherId).trim(); profileId = String(profileId).trim(); - if (!isFn(continueAuction)) { - logError(`${CONSTANTS.LOG_PRE_FIX} continueAuction is not a function. Please ensure to add priceFloors module.`); - return false; - } - - _fetchFloorRulesPromise = fetchData(publisherId, profileId, "FLOORS"); - _fetchConfigPromise = fetchData(publisherId, profileId, "CONFIGS"); - - _fetchConfigPromise.then(async (profileConfigs) => { - const auctionDelay = conf?.getConfig('realTimeData')?.auctionDelay || 300; - const maxWaitTime = 0.8 * auctionDelay; - - const elapsedTime = Date.now() - initTime; - const remainingTime = Math.max(maxWaitTime - elapsedTime, 0); - const floorsData = await withTimeout(_fetchFloorRulesPromise, remainingTime); - - // Store the profile configs globally - setProfileConfigs(profileConfigs); - - const floorsConfig = getFloorsConfig(floorsData, profileConfigs); - floorsConfig && conf?.setConfig(floorsConfig); - configMerged(); - }); - + // Fetch configuration and initialize plugins + _ymConfigPromise = configJsonManager.fetchConfig(publisherId, profileId) + .then(success => { + if (!success) { + return Promise.reject(new Error('Failed to fetch configuration')); + } + return pluginManager.initialize(configJsonManager); + }); return true; }; @@ -534,34 +132,31 @@ const init = (config, _userConsent) => { * @param {function} callback */ const getBidRequestData = (reqBidsConfigObj, callback) => { - configMergedPromise.then(() => { - const hookConfig = { - reqBidsConfigObj, - context: this, - nextFn: () => true, - haveExited: false, - timer: null - }; - continueAuction(hookConfig); - if (_country) { + _ymConfigPromise.then(() => { + pluginManager.executeHook('processBidRequest', reqBidsConfigObj); + // Apply country information if available + const country = configJsonManager.country; + if (country) { const ortb2 = { user: { ext: { - ctr: _country, + ctr: country, } } - } + }; - mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { - [CONSTANTS.SUBMODULE_NAME]: ortb2 - }); + reqBidsConfigObj.ortb2Fragments.bidder[CONSTANTS.SUBMODULE_NAME] = mergeDeep( + reqBidsConfigObj.ortb2Fragments.bidder[CONSTANTS.SUBMODULE_NAME] || {}, + ortb2 + ); } + callback(); - }).catch((error) => { - logError(CONSTANTS.LOG_PRE_FIX, 'Error in updating floors :', error); + }).catch(error => { + logError(CONSTANTS.LOG_PRE_FIX, error); callback(); }); -} +}; /** * Returns targeting data for ad units @@ -572,62 +167,7 @@ const getBidRequestData = (reqBidsConfigObj, callback) => { * @return {Object} - Targeting data for ad units */ export const getTargetingData = (adUnitCodes, config, userConsent, auction) => { - // Access the profile configs stored globally - const profileConfigs = getProfileConfigs(); - - // Return empty object if profileConfigs is undefined or pmTargetingKeys.enabled is explicitly set to false - if (!profileConfigs || profileConfigs?.plugins?.dynamicFloors?.pmTargetingKeys?.enabled === false) { - logInfo(`${CONSTANTS.LOG_PRE_FIX} pmTargetingKeys is disabled or profileConfigs is undefined`); - return {}; - } - - // Helper to check if RTD floor is applied to a bid - const isRtdFloorApplied = bid => bid.floorData?.floorProvider === "PM" && !bid.floorData.skipped; - - // Check if any bid has RTD floor applied - const hasRtdFloorAppliedBid = - auction?.adUnits?.some(adUnit => adUnit.bids?.some(isRtdFloorApplied)) || - auction?.bidsReceived?.some(isRtdFloorApplied); - - // Only log when RTD floor is applied - if (hasRtdFloorAppliedBid) { - logInfo(CONSTANTS.LOG_PRE_FIX, 'Setting targeting via getTargetingData:'); - } - - // Process each ad unit code - const targeting = {}; - - adUnitCodes.forEach(code => { - targeting[code] = {}; - - // For non-RTD floor applied cases, only set pm_ym_flrs to 0 - if (!hasRtdFloorAppliedBid) { - targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 0; - return; - } - - // Find bids and determine status for RTD floor applied cases - const bidsForAdUnit = findBidsForAdUnit(auction, code); - const rejectedBidsForAdUnit = findRejectedBidsForAdUnit(auction, code); - const rejectedFloorBid = findRejectedFloorBid(rejectedBidsForAdUnit); - const winningBid = findWinningBid(code); - - // Determine bid status and values - const { bidStatus, baseValue, multiplier } = determineBidStatusAndValues( - winningBid, - rejectedFloorBid, - bidsForAdUnit, - auction, - code - ); - - // Set all targeting keys - targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRS] = 1; - targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_FLRV] = (baseValue * multiplier).toFixed(2); - targeting[code][CONSTANTS.TARGETING_KEYS.PM_YM_BID_S] = bidStatus; - }); - - return targeting; + return pluginManager.executeHook('getTargeting', adUnitCodes, config, userConsent, auction); }; export const pubmaticSubmodule = { diff --git a/modules/pubstackBidAdapter.md b/modules/pubstackBidAdapter.md new file mode 100644 index 00000000000..dc3df3b8ee5 --- /dev/null +++ b/modules/pubstackBidAdapter.md @@ -0,0 +1,30 @@ +# Overview +``` +Module Name: Pubstack Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@pubstack.io +``` + +# Description +Connects to Pubstack exchange for bids. + +Pubstack bid adapter supports all media type including video, banner and native. + +# Test Parameters +``` +var adUnits = [{ + code: 'adunit-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'pubstack', + params: { + siteId: 'your-site-id', + adUnitName: 'adunit-1' + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/pubstackBidAdapter.ts b/modules/pubstackBidAdapter.ts new file mode 100644 index 00000000000..62126136e72 --- /dev/null +++ b/modules/pubstackBidAdapter.ts @@ -0,0 +1,156 @@ +import { canAccessWindowTop, deepSetValue, getWindowSelf, getWindowTop, logError } from '../src/utils.js'; +import { AdapterRequest, BidderSpec, registerBidder, ServerResponse } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { getPlacementPositionUtils } from '../libraries/placementPositionInfo/placementPositionInfo.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBRequest } from '../src/prebid.public.js'; +import { config } from '../src/config.js'; +import { SyncType } from '../src/userSync.js'; +import { ConsentData, CONSENT_GDPR, CONSENT_USP, CONSENT_GPP } from '../src/consentHandler.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const BIDDER_CODE = 'pubstack'; +const GVLID = 1408; +const REQUEST_URL = 'https://node.pbstck.com/openrtb2/auction'; +const COOKIESYNC_IFRAME_URL = 'https://cdn.pbstck.com/async_usersync.html'; +const COOKIESYNC_PIXEL_URL = 'https://cdn.pbstck.com/async_usersync.png'; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: { + siteId: string; + adUnitName: string; + }; + } +} + +type GetUserSyncFn = ( + syncOptions: { + iframeEnabled: boolean; + pixelEnabled: boolean; + }, + responses: ServerResponse[], + gdprConsent: null | ConsentData[typeof CONSENT_GDPR], + uspConsent: null | ConsentData[typeof CONSENT_USP], + gppConsent: null | ConsentData[typeof CONSENT_GPP]) => ({ type: SyncType, url: string })[] + +const siteIds: Set = new Set(); +let cntRequest = 0; +let cntTimeouts = 0; +const { getPlacementEnv, getPlacementInfo } = getPlacementPositionUtils(); + +const getElementForAdUnitCode = (adUnitCode: string): HTMLElement | undefined => { + if (!adUnitCode) return; + const win = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); + const doc = win?.document; + let element = doc?.getElementById(adUnitCode) as HTMLElement | null; + if (element) return element; + const divId = getGptSlotInfoForAdUnitCode(adUnitCode)?.divId; + element = divId ? doc?.getElementById(divId) as HTMLElement | null : null; + if (element) return element; +}; + +const converter = ortbConverter({ + imp(buildImp, bidRequest: BidRequest, context) { + const element = getElementForAdUnitCode(bidRequest.adUnitCode); + const placementInfo = getPlacementInfo(bidRequest); + const imp = buildImp(bidRequest, context); + deepSetValue(imp, `ext.prebid.bidder.${BIDDER_CODE}.adUnitName`, bidRequest.params.adUnitName); + deepSetValue(imp, `ext.prebid.placement.code`, bidRequest.adUnitCode); + deepSetValue(imp, `ext.prebid.placement.domId`, element?.id); + deepSetValue(imp, `ext.prebid.placement.viewability`, placementInfo.PlacementPercentView); + deepSetValue(imp, `ext.prebid.placement.viewportDistance`, placementInfo.DistanceToView); + deepSetValue(imp, `ext.prebid.placement.height`, placementInfo.ElementHeight); + deepSetValue(imp, `ext.prebid.placement.auctionsCount`, placementInfo.AuctionsCount); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + cntRequest++; + const placementEnv = getPlacementEnv(); + const request = buildRequest(imps, bidderRequest, context) + const siteId = bidderRequest.bids[0].params.siteId + siteIds.add(siteId); + deepSetValue(request, 'site.publisher.id', siteId); + deepSetValue(request, 'test', config.getConfig('debug') ? 1 : 0); + deepSetValue(request, 'ext.prebid.version', getGlobal()?.version ?? 'unknown'); + deepSetValue(request, `ext.prebid.request.count`, cntRequest); + deepSetValue(request, `ext.prebid.request.timeoutCount`, cntTimeouts); + deepSetValue(request, `ext.prebid.page.tabActive`, placementEnv.TabActive); + deepSetValue(request, `ext.prebid.page.height`, placementEnv.PageHeight); + deepSetValue(request, `ext.prebid.page.viewportHeight`, placementEnv.ViewportHeight); + deepSetValue(request, `ext.prebid.page.timeFromNavigation`, placementEnv.TimeFromNavigation); + return request; + }, +}); + +const isBidRequestValid = (bid: BidRequest): boolean => { + if (!bid.params.siteId || typeof bid.params.siteId !== 'string') { + logError('bid.params.siteId needs to be a string'); + if (config.getConfig('debug') === false) return false; + } + if (!bid.params.adUnitName || typeof bid.params.adUnitName !== 'string') { + logError('bid.params.adUnitName needs to be a string'); + if (config.getConfig('debug') === false) return false; + } + return true; +}; + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data: ORTBRequest = converter.toORTB({ bidRequests, bidderRequest }); + const siteId = data.site.publisher.id; + return { + method: 'POST', + url: `${REQUEST_URL}?siteId=${siteId}`, + data, + }; +}; + +const interpretResponse = (serverResponse, bidRequest) => { + if (!serverResponse?.body) { + return []; + } + return converter.fromORTB({ request: bidRequest.data, response: serverResponse.body }); +}; + +const getUserSyncs: GetUserSyncFn = (syncOptions, _serverResponses, gdprConsent, uspConsent, gppConsent) => { + const isIframeEnabled = syncOptions.iframeEnabled; + const isPixelEnabled = syncOptions.pixelEnabled; + + if (!isIframeEnabled && !isPixelEnabled) { + return []; + } + + const payload = btoa(JSON.stringify({ + gdprConsentString: gdprConsent?.consentString, + gdprApplies: gdprConsent?.gdprApplies, + uspConsent, + gpp: gppConsent?.gppString, + gpp_sid: gppConsent?.applicableSections + + })); + const syncUrl = isIframeEnabled ? COOKIESYNC_IFRAME_URL : COOKIESYNC_PIXEL_URL; + + return Array.from(siteIds).map(siteId => ({ + type: isIframeEnabled ? 'iframe' : 'image', + url: `${syncUrl}?consent=${payload}&siteId=${siteId}`, + })); +}; + +export const spec: BidderSpec = { + code: BIDDER_CODE, + aliases: [{code: `${BIDDER_CODE}_server`, gvlid: GVLID}], + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onTimeout: () => cntTimeouts++, +}; + +registerBidder(spec); diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 7cb97579394..d8028b6c1c9 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -294,12 +294,12 @@ function app(bidderRequest) { } function isMobile() { - return /(ios|ipod|ipad|iphone|android)/i.test(global.navigator.userAgent); + return /(ios|ipod|ipad|iphone|android)/i.test(window.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 + window.navigator.userAgent ); } diff --git a/modules/revantageBidAdapter.js b/modules/revantageBidAdapter.js new file mode 100644 index 00000000000..0a0186d5b59 --- /dev/null +++ b/modules/revantageBidAdapter.js @@ -0,0 +1,407 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone, deepAccess, logWarn, logError, triggerPixel } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'revantage'; +const ENDPOINT_URL = 'https://bid.revantage.io/bid'; +const SYNC_URL = 'https://sync.revantage.io/sync'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.feedId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + // Handle null/empty bid requests + if (!validBidRequests || validBidRequests.length === 0) { + return []; + } + + // All bid requests in a batch must have the same feedId + // If not, we log a warning and return an empty array + const feedId = validBidRequests[0]?.params?.feedId; + const allSameFeedId = validBidRequests.every(bid => bid.params.feedId === feedId); + if (!allSameFeedId) { + logWarn('Revantage: All bid requests in a batch must have the same feedId'); + return []; + } + + try { + const openRtbBidRequest = makeOpenRtbRequest(validBidRequests, bidderRequest); + return { + method: 'POST', + url: ENDPOINT_URL + '?feed=' + encodeURIComponent(feedId), + data: JSON.stringify(openRtbBidRequest), + options: { + contentType: 'text/plain', + withCredentials: false + }, + bidRequests: validBidRequests + }; + } catch (e) { + logError('Revantage: buildRequests failed', e); + return []; + } + }, + + interpretResponse: function(serverResponse, request) { + const bids = []; + const resp = serverResponse.body; + const originalBids = request.bidRequests || []; + const bidIdMap = {}; + originalBids.forEach(b => { bidIdMap[b.bidId] = b; }); + + if (!resp || !Array.isArray(resp.seatbid)) return bids; + + resp.seatbid.forEach(seatbid => { + if (Array.isArray(seatbid.bid)) { + seatbid.bid.forEach(rtbBid => { + const originalBid = bidIdMap[rtbBid.impid]; + if (!originalBid || !rtbBid.price || rtbBid.price <= 0) return; + + // Check for ad markup + const hasAdMarkup = !!(rtbBid.adm || rtbBid.vastXml || rtbBid.vastUrl); + if (!hasAdMarkup) { + logWarn('Revantage: No ad markup in bid'); + return; + } + + const bidResponse = { + requestId: originalBid.bidId, + cpm: rtbBid.price, + width: rtbBid.w || getFirstSize(originalBid, 0, 300), + height: rtbBid.h || getFirstSize(originalBid, 1, 250), + creativeId: rtbBid.crid || rtbBid.id || rtbBid.adid || 'revantage-' + Date.now(), + dealId: rtbBid.dealid, + currency: resp.cur || 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: rtbBid.adomain || [], + dsp: seatbid.seat || 'unknown', + networkName: 'Revantage' + } + }; + + // Add burl for server-side win notification + if (rtbBid.burl) { + bidResponse.burl = rtbBid.burl; + } + + // Determine if this is a video bid + // FIX: Check for VAST content in adm even for multi-format ad units + const isVideo = (rtbBid.ext && rtbBid.ext.mediaType === 'video') || + rtbBid.vastXml || rtbBid.vastUrl || + isVastAdm(rtbBid.adm) || + (originalBid.mediaTypes && originalBid.mediaTypes.video && + !originalBid.mediaTypes.banner); + + if (isVideo) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = rtbBid.vastXml || rtbBid.adm; + bidResponse.vastUrl = rtbBid.vastUrl; + + if (!bidResponse.vastUrl && !bidResponse.vastXml) { + logWarn('Revantage: Video bid missing VAST content'); + return; + } + } else { + bidResponse.mediaType = BANNER; + bidResponse.ad = rtbBid.adm; + + if (!bidResponse.ad) { + logWarn('Revantage: Banner bid missing ad markup'); + return; + } + } + + // Add DSP price if available + if (rtbBid.ext && rtbBid.ext.dspPrice) { + bidResponse.meta.dspPrice = rtbBid.ext.dspPrice; + } + + bids.push(bidResponse); + }); + } + }); + + return bids; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + let params = '?cb=' + new Date().getTime(); + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + } + if (typeof gdprConsent.consentString === 'string') { + params += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString); + } + } + + if (uspConsent && typeof uspConsent === 'string') { + params += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + if (gppConsent) { + if (gppConsent.gppString) { + params += '&gpp=' + encodeURIComponent(gppConsent.gppString); + } + if (gppConsent.applicableSections) { + params += '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(',')); + } + } + + if (syncOptions.iframeEnabled) { + syncs.push({ type: 'iframe', url: SYNC_URL + params }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ type: 'image', url: SYNC_URL + params + '&tag=img' }); + } + + return syncs; + }, + + onBidWon: function(bid) { + if (bid.burl) { + triggerPixel(bid.burl); + } + } +}; + +// === MAIN RTB BUILDER === +function makeOpenRtbRequest(validBidRequests, bidderRequest) { + const imp = validBidRequests.map(bid => { + const sizes = getSizes(bid); + const floor = getBidFloorEnhanced(bid); + + const impression = { + id: bid.bidId, + tagid: bid.adUnitCode, + bidfloor: floor, + ext: { + feedId: deepAccess(bid, 'params.feedId'), + bidder: { + placementId: deepAccess(bid, 'params.placementId'), + publisherId: deepAccess(bid, 'params.publisherId') + } + } + }; + + // Add banner specs + if (bid.mediaTypes && bid.mediaTypes.banner) { + impression.banner = { + w: sizes[0][0], + h: sizes[0][1], + format: sizes.map(size => ({ w: size[0], h: size[1] })) + }; + } + + // Add video specs + if (bid.mediaTypes && bid.mediaTypes.video) { + const video = bid.mediaTypes.video; + impression.video = { + mimes: video.mimes || ['video/mp4', 'video/webm'], + minduration: video.minduration || 0, + maxduration: video.maxduration || 60, + protocols: video.protocols || [2, 3, 5, 6], + w: getVideoSize(video.playerSize, 0, 640), + h: getVideoSize(video.playerSize, 1, 360), + placement: video.placement || 1, + playbackmethod: video.playbackmethod || [1, 2], + api: video.api || [1, 2], + skip: video.skip || 0, + skipmin: video.skipmin || 0, + skipafter: video.skipafter || 0, + pos: video.pos || 0, + startdelay: video.startdelay || 0, + linearity: video.linearity || 1 + }; + } + + return impression; + }); + + let user = {}; + if (validBidRequests[0] && validBidRequests[0].userIdAsEids) { + user.eids = deepClone(validBidRequests[0].userIdAsEids); + } + + const ortb2 = bidderRequest.ortb2 || {}; + const site = { + domain: typeof window !== 'undefined' ? window.location.hostname : '', + page: typeof window !== 'undefined' ? window.location.href : '', + ref: typeof document !== 'undefined' ? document.referrer : '' + }; + + // Merge ortb2 site data + if (ortb2.site) { + Object.assign(site, deepClone(ortb2.site)); + } + + const device = deepClone(ortb2.device) || {}; + // Add basic device info if not present + if (!device.ua) { + device.ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; + } + if (!device.language) { + device.language = typeof navigator !== 'undefined' ? navigator.language : ''; + } + if (!device.w) { + device.w = typeof screen !== 'undefined' ? screen.width : 0; + } + if (!device.h) { + device.h = typeof screen !== 'undefined' ? screen.height : 0; + } + if (!device.devicetype) { + device.devicetype = getDeviceType(); + } + + const regs = { ext: {} }; + if (bidderRequest.gdprConsent) { + regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + user.ext = { consent: bidderRequest.gdprConsent.consentString }; + } + if (bidderRequest.uspConsent) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // Add GPP consent + if (bidderRequest.gppConsent) { + if (bidderRequest.gppConsent.gppString) { + regs.ext.gpp = bidderRequest.gppConsent.gppString; + } + if (bidderRequest.gppConsent.applicableSections) { + // Send as array, not comma-separated string + regs.ext.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + } + + // Get supply chain + const schain = bidderRequest.schain || (validBidRequests[0] && validBidRequests[0].schain); + + return { + id: bidderRequest.auctionId, + imp: imp, + site: site, + device: device, + user: user, + regs: regs, + schain: schain, + tmax: bidderRequest.timeout || 1000, + cur: ['USD'], + ext: { + prebid: { + version: '$prebid.version$' + } + } + }; +} + +// === UTILS === +function getSizes(bid) { + if (bid.mediaTypes && bid.mediaTypes.banner && Array.isArray(bid.mediaTypes.banner.sizes) && bid.mediaTypes.banner.sizes.length > 0) { + return bid.mediaTypes.banner.sizes; + } + if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.playerSize && bid.mediaTypes.video.playerSize.length > 0) { + return bid.mediaTypes.video.playerSize; + } + if (bid.sizes && bid.sizes.length > 0) { + return bid.sizes; + } + return [[300, 250]]; +} + +function getFirstSize(bid, index, defaultVal) { + const sizes = getSizes(bid); + return (sizes && sizes[0] && sizes[0][index]) || defaultVal; +} + +/** + * Safely extract video dimensions from playerSize. + * Handles both nested [[640, 480]] and flat [640, 480] formats. + * @param {Array} playerSize - video.playerSize from mediaTypes config + * @param {number} index - 0 for width, 1 for height + * @param {number} defaultVal - fallback value + * @returns {number} + */ +function getVideoSize(playerSize, index, defaultVal) { + if (!playerSize || !Array.isArray(playerSize) || playerSize.length === 0) { + return defaultVal; + } + // Nested: [[640, 480]] or [[640, 480], [320, 240]] + if (Array.isArray(playerSize[0])) { + return playerSize[0][index] || defaultVal; + } + // Flat: [640, 480] + if (typeof playerSize[0] === 'number') { + return playerSize[index] || defaultVal; + } + return defaultVal; +} + +/** + * Detect if adm content is VAST XML (for multi-format video detection). + * @param {string} adm - ad markup string + * @returns {boolean} + */ +function isVastAdm(adm) { + if (typeof adm !== 'string') return false; + const trimmed = adm.trim(); + return trimmed.startsWith(' floor && floorInfo.currency === 'USD' && !isNaN(floorInfo.floor)) { + floor = floorInfo.floor; + } + } catch (e) { + // Continue to next size + } + } + + // Fallback to general floor + if (floor === 0) { + try { + const floorInfo = bid.getFloor({ currency: 'USD', mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(floorInfo.floor)) { + floor = floorInfo.floor; + } + } catch (e) { + logWarn('Revantage: getFloor threw error', e); + } + } + } + return floor; +} + +function getDeviceType() { + if (typeof screen === 'undefined') return 1; + const width = screen.width; + const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; + + if (/iPhone|iPod/i.test(ua) || (width < 768 && /Mobile/i.test(ua))) return 2; // Mobile + if (/iPad/i.test(ua) || (width >= 768 && width < 1024)) return 5; // Tablet + return 1; // Desktop/PC +} + +// === REGISTER === +registerBidder(spec); diff --git a/modules/revantageBidAdapter.md b/modules/revantageBidAdapter.md new file mode 100644 index 00000000000..42a4ef4198d --- /dev/null +++ b/modules/revantageBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: ReVantage Bidder Adapter +Module Type: ReVantage Bidder Adapter +Maintainer: bern@revantage.io +``` + +# Description + +Connects to ReVantage exchange for bids. +ReVantage bid adapter supports Banner and Video. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'revantage', + params: { + feedId: 'testfeed', + } + } + ] + } +``` diff --git a/modules/revnewBidAdapter.md b/modules/revnewBidAdapter.md new file mode 100644 index 00000000000..fa5e8ab327b --- /dev/null +++ b/modules/revnewBidAdapter.md @@ -0,0 +1,57 @@ +# Overview + +``` +Module Name: Revnew Bid Adapter +Module Type: Bidder Adapter +Maintainer: gabriel@nexx360.io +``` + +# Description + +Connects to Revnew network for bids. + +To use us as a bidder you must have an account and an active "tagId" or "placement" on our platform. + +# Test Parameters + +## Web + +### Display +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'revnew', + params: { + tagId: 'testnexx' + } + }] + }, +]; +``` + +### Video Instream +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'revnew', + params: { + placement: 'TEST_PLACEMENT' + } + }] + }; +``` diff --git a/modules/revnewBidAdapter.ts b/modules/revnewBidAdapter.ts new file mode 100644 index 00000000000..ecdcdb5e845 --- /dev/null +++ b/modules/revnewBidAdapter.ts @@ -0,0 +1,97 @@ +import { deepSetValue, generateUUID, logError } from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {AdapterRequest, BidderSpec, registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; + +const BIDDER_CODE = 'revnew'; +const REQUEST_URL = 'https://fast.nexx360.io/revnew'; +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '1.0'; +const GVLID = 1468; +const REVNEW_KEY = 'revnew_storage'; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type RevnewBidParams = RequireAtLeastOne<{ + tagId?: string; + placement?: string; + customId?: string; +}, "tagId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: RevnewBidParams; + } +} + +export const STORAGE = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +export const getRevnewLocalStorage = getLocalStorageFunctionGenerator<{ revnewId: string }>( + STORAGE, + BIDDER_CODE, + REVNEW_KEY, + 'revnewId' +); + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 120, + }, + imp(buildImp, bidRequest: BidRequest, context) { + let imp:ORTBImp = buildImp(bidRequest, context); + imp = enrichImp(imp, bidRequest); + deepSetValue(imp, 'ext.revnew', bidRequest.params); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + let request:ORTBRequest = buildRequest(imps, bidderRequest, context); + const amxId = getAmxId(STORAGE, BIDDER_CODE); + request = enrichRequest(request, amxId, PAGE_VIEW_ID, BIDDER_VERSION); + return request; + }, +}); + +const isBidRequestValid = (bid:BidRequest): boolean => { + if (!bid.params.tagId && !bid.params.placement) { + logError('bid.params.tagId or bid.params.placement must be defined'); + return false; + } + return true; +}; + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data:ORTBRequest = converter.toORTB({bidRequests, bidderRequest}) + const adapterRequest:AdapterRequest = { + method: 'POST', + url: REQUEST_URL, + data, + } + return adapterRequest; +}; + +export const spec:BidderSpec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 4d61d827650..cb551bb0b62 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -52,7 +52,7 @@ export const spec = { demand: raiGetDemandType(bid), videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), - cpuc: (typeof window.navigator !== 'undefined' ? window.navigator.hardwareConcurrency : null), + cpuc: null, kws: bid.params.keywords, schain: bid?.ortb2?.source?.ext?.schain, gpid: raiSetPbAdSlot(bid), diff --git a/modules/ringieraxelspringerBidAdapter.js b/modules/ringieraxelspringerBidAdapter.js index 10cccecccf4..c5b7e000f87 100644 --- a/modules/ringieraxelspringerBidAdapter.js +++ b/modules/ringieraxelspringerBidAdapter.js @@ -1,402 +1,8 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { - isEmpty, - parseSizesInput, - deepAccess -} 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 = 'ringieraxelspringer'; -const VERSION = '1.0'; - -const getEndpoint = (network) => { - return `https://csr.onet.pl/${encodeURIComponent(network)}/csr-006/csr.json?nid=${encodeURIComponent(network)}&`; -}; - -function parseParams(params, bidderRequest) { - const newParams = {}; - if (params.customParams && typeof params.customParams === 'object') { - for (const param in params.customParams) { - if (params.customParams.hasOwnProperty(param)) { - newParams[param] = params.customParams[param]; - } - } - } - const du = deepAccess(bidderRequest, 'refererInfo.page'); - const dr = deepAccess(bidderRequest, 'refererInfo.ref'); - if (du) { - newParams.du = du; - } - if (dr) { - newParams.dr = dr; - } - const pageContext = params.pageContext; - if (!pageContext) { - return newParams; - } - if (pageContext.du) { - newParams.du = pageContext.du; - } - if (pageContext.dr) { - newParams.dr = pageContext.dr; - } - if (pageContext.dv) { - newParams.DV = pageContext.dv; - } - const keywords = getAllOrtbKeywords(bidderRequest?.ortb2, pageContext.keyWords) - if (keywords.length > 0) { - newParams.kwrd = keywords.join('+') - } - if (pageContext.capping) { - newParams.local_capping = pageContext.capping; - } - if (pageContext.keyValues && typeof pageContext.keyValues === 'object') { - for (const param in pageContext.keyValues) { - if (pageContext.keyValues.hasOwnProperty(param)) { - const kvName = 'kv' + param; - newParams[kvName] = pageContext.keyValues[param]; - } - } - } - if (bidderRequest?.ortb2?.regs?.ext?.dsa?.required !== undefined) { - newParams.dsainfo = bidderRequest?.ortb2?.regs?.ext?.dsa?.required; - } - return newParams; -} - /** - * @param url string - * @param type number // 1 - img, 2 - js - * @returns an object { event: 1, method: 1 or 2, url: 'string' } + * Backward-compatibility shim for ringieraxelspringer bidder. + * This bidder has been renamed to 'das'. + * + * This file will be removed in Prebid 11. + * See dasBidAdapter.js for implementation. */ -function prepareItemEventtrackers(url, type) { - return { - event: 1, - method: type, - url: url - }; -} - -function prepareEventtrackers(emsLink, imp, impression, impression1, impressionJs1) { - const eventtrackers = [prepareItemEventtrackers(emsLink, 1)]; - - if (imp) { - eventtrackers.push(prepareItemEventtrackers(imp, 1)); - } - - if (impression) { - eventtrackers.push(prepareItemEventtrackers(impression, 1)); - } - - if (impression1) { - eventtrackers.push(prepareItemEventtrackers(impression1, 1)); - } - - if (impressionJs1) { - eventtrackers.push(prepareItemEventtrackers(impressionJs1, 2)); - } - - return eventtrackers; -} - -function parseOrtbResponse(ad) { - if (!(ad.data?.fields && ad.data?.meta)) { - return false; - } - - 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: 0, - data: { - value: body || '', - type: 2 - }, - }, - { - id: 1, - data: { - value: adInfo || '', - // Body2 type - type: 10 - }, - }, - { - id: 3, - img: { - type: 1, - url: partnerLogo || '', - w: width, - h: height - } - }, - { - id: 4, - img: { - type: 3, - url: image || Image || '', - w: width, - h: height - } - }, - { - id: 5, - data: { - value: deepAccess(ad, 'data.meta.advertiser_name', null), - type: 1 - } - }, - { - id: 6, - title: { - text: title || Headline || '' - } - }, - ], - link: { - url: link, - clicktrackers - }, - eventtrackers - }; - - if (dsaurl) { - ortb.privacy = dsaurl - } - - return ortb -} - -function parseNativeResponse(ad) { - if (!(ad.data?.fields && ad.data?.meta)) { - return false; - } - - 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 = { - title: title || Headline || '', - image: { - url: image || Image || '', - 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) - }; - - if (dsaurl) { - nativeResponse.privacyLink = dsaurl; - } - - return nativeResponse -} - -const buildBid = (ad, mediaType) => { - if (ad.type === 'empty' || mediaType === undefined) { - return null; - } - - const data = { - requestId: ad.id, - cpm: ad.bid_rate ? ad.bid_rate.toFixed(2) : 0, - ttl: 300, - creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0, - netRevenue: true, - currency: ad.currency || 'USD', - dealId: ad.prebid_deal || null, - actgMatch: ad.actg_match || 0, - meta: { mediaType: BANNER }, - mediaType: BANNER, - ad: ad.html || null, - width: ad.width || 0, - height: ad.height || 0 - } - - if (mediaType === 'native') { - data.meta = { mediaType: NATIVE }; - data.mediaType = NATIVE; - data.native = parseNativeResponse(ad) || {}; - - delete data.ad; - } - - return data; -}; - -const getContextParams = (bidRequests, bidderRequest) => { - const bid = bidRequests[0]; - const { params } = bid; - const requestParams = { - site: params.site, - area: params.area, - cre_format: 'html', - systems: 'das', - kvprver: VERSION, - ems_url: 1, - bid_rate: 1, - ...parseParams(params, bidderRequest) - }; - return Object.keys(requestParams).map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(requestParams[key])).join('&'); -}; - -const getSlots = (bidRequests) => { - let queryString = ''; - const batchSize = bidRequests.length; - for (let i = 0; i < batchSize; i++) { - const adunit = bidRequests[i]; - const slotSequence = deepAccess(adunit, 'params.slotSequence'); - const creFormat = getAdUnitCreFormat(adunit); - const sizes = creFormat === 'native' ? 'fluid' : parseSizesInput(getAdUnitSizes(adunit)).join(','); - - queryString += `&slot${i}=${encodeURIComponent(adunit.params.slot)}&id${i}=${encodeURIComponent(adunit.bidId)}&composition${i}=CHILD`; - - if (creFormat === 'native') { - queryString += `&cre_format${i}=native`; - } - - queryString += `&kvhb_format${i}=${creFormat === 'native' ? 'native' : 'banner'}`; - - if (sizes) { - queryString += `&iusizes${i}=${encodeURIComponent(sizes)}`; - } - - if (slotSequence !== undefined && slotSequence !== null) { - queryString += `&pos${i}=${encodeURIComponent(slotSequence)}`; - } - } - - return queryString; -}; - -const getGdprParams = (bidderRequest) => { - const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); - const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); - let queryString = ''; - if (gdprApplies !== undefined) { - queryString += `&gdpr_applies=${encodeURIComponent(gdprApplies)}`; - } - if (consentString !== undefined) { - queryString += `&euconsent=${encodeURIComponent(consentString)}`; - } - return queryString; -}; - -const parseAuctionConfigs = (serverResponse, bidRequest) => { - if (isEmpty(bidRequest)) { - return null; - } - const auctionConfigs = []; - const gctx = serverResponse && serverResponse.body?.gctx; - - bidRequest.bidIds.filter(bid => bid.fledgeEnabled).forEach((bid) => { - auctionConfigs.push({ - 'bidId': bid.bidId, - 'config': { - '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, - 'gctx': gctx - } - } - }); - }); - - if (auctionConfigs.length === 0) { - return null; - } else { - return auctionConfigs; - } -} - -const getAdUnitCreFormat = (adUnit) => { - if (!adUnit) { - return; - } - - let creFormat = 'html'; - const mediaTypes = Object.keys(adUnit.mediaTypes); - - if (mediaTypes && mediaTypes.length === 1 && mediaTypes.includes('native')) { - creFormat = 'native'; - } - - return creFormat; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], - - isBidRequestValid: function (bidRequest) { - if (!bidRequest || !bidRequest.params || typeof bidRequest.params !== 'object') { - return; - } - const { params } = bidRequest; - return Boolean(params.network && params.site && params.area && params.slot); - }, - - buildRequests: function (bidRequests, bidderRequest) { - const slotsQuery = getSlots(bidRequests); - const contextQuery = getContextParams(bidRequests, bidderRequest); - const gdprQuery = getGdprParams(bidderRequest); - const fledgeEligible = Boolean(bidderRequest?.paapi?.enabled); - const network = bidRequests[0].params.network; - const bidIds = bidRequests.map((bid) => ({ - slot: bid.params.slot, - bidId: bid.bidId, - sizes: getAdUnitSizes(bid), - params: bid.params, - fledgeEnabled: fledgeEligible, - mediaType: (bid.mediaTypes && bid.mediaTypes.banner) ? 'display' : NATIVE - })); - - return [{ - method: 'GET', - url: getEndpoint(network) + contextQuery + slotsQuery + gdprQuery, - bidIds: bidIds - }]; - }, - - interpretResponse: function (serverResponse, bidRequest) { - const response = serverResponse.body; - const fledgeAuctionConfigs = parseAuctionConfigs(serverResponse, bidRequest); - const bids = (!response || !response.ads || response.ads.length === 0) ? [] : response.ads.map((ad, index) => buildBid( - ad, - bidRequest?.bidIds?.[index]?.mediaType || 'banner' - )).filter((bid) => !isEmpty(bid)); - - if (fledgeAuctionConfigs) { - // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. - return {bids, paapi: fledgeAuctionConfigs}; - } else { - return bids; - } - } -}; - -registerBidder(spec); +export { spec } from './dasBidAdapter.js'; // eslint-disable-line prebid/validate-imports diff --git a/modules/ringieraxelspringerBidAdapter.md b/modules/ringieraxelspringerBidAdapter.md index b3a716f9f56..4527d9f8c6d 100644 --- a/modules/ringieraxelspringerBidAdapter.md +++ b/modules/ringieraxelspringerBidAdapter.md @@ -1,52 +1,8 @@ # Overview -``` -Module Name: Ringier Axel Springer Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support@ringpublishing.com -``` +The `ringieraxelspringer` bidder has been renamed to `das`. +Please use the `das` bidder code instead. -# Description +See [dasBidAdapter.md](./dasBidAdapter.md) for documentation. -Module that connects to Ringer Axel Springer demand sources. -Only banner and native format is supported. - -# Test Parameters -```js -var adUnits = [{ - code: 'test-div-ad', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bids: [{ - bidder: 'ringieraxelspringer', - params: { - network: '4178463', - site: 'test', - area: 'areatest', - slot: 'slot' - } - }] -}]; -``` - -# Parameters - -| Name | Scope | Type | Description | Example | -|------------------------------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| -| 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 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"` | -| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"` | -| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]` | -| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` | -| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` | -| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` | -| customParams | optional | Object | Custom request params | `{}` | +This adapter will be removed in Prebid 11. diff --git a/modules/rtdModule/index.ts b/modules/rtdModule/index.ts index 73473b6d163..1b3bff0baf3 100644 --- a/modules/rtdModule/index.ts +++ b/modules/rtdModule/index.ts @@ -2,8 +2,8 @@ import {config} from '../../src/config.js'; import {getHook, module} from '../../src/hook.js'; import {logError, logInfo, logWarn, mergeDeep} from '../../src/utils.js'; import * as events from '../../src/events.js'; -import { EVENTS, JSON_MAPPING } from '../../src/constants.js'; -import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; +import {EVENTS, JSON_MAPPING} from '../../src/constants.js'; +import adapterManager, {gdprDataHandler, gppDataHandler, uspDataHandler} from '../../src/adapterManager.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; import {GDPR_GVLIDS} from '../../src/consentHandler.js'; import {MODULE_TYPE_RTD} from '../../src/activities/modules.js'; @@ -163,10 +163,31 @@ export const setBidRequestsData = timedAuctionHook('rtd', function setBidRequest const timeout = shouldDelayAuction ? _moduleConfig.auctionDelay : 0; waitTimeout = setTimeout(exitHook, timeout); + const fpdKey = 'ortb2Fragments'; relevantSubModules.forEach(sm => { - const fpdGuard = guardOrtb2Fragments(reqBidsConfigObj.ortb2Fragments || {}, activityParams(MODULE_TYPE_RTD, sm.name)); - sm.getBidRequestData({...reqBidsConfigObj, ortb2Fragments: fpdGuard}, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent, timeout); + const fpdGuard = guardOrtb2Fragments(reqBidsConfigObj[fpdKey] ?? {}, activityParams(MODULE_TYPE_RTD, sm.name)); + // submodules need to be able to modify the request object, but we need + // to protect the FPD portion of it. Use a proxy that passes through everything + // except 'ortb2Fragments'. + const request = new Proxy(reqBidsConfigObj, { + get(target, prop, receiver) { + if (prop === fpdKey) return fpdGuard; + return Reflect.get(target, prop, receiver); + }, + set(target, prop, value, receiver) { + if (prop === fpdKey) { + mergeDeep(fpdGuard, value); + return true; + } + return Reflect.set(target, prop, value, receiver); + }, + deleteProperty(target, prop) { + if (prop === fpdKey) return true; + return Reflect.deleteProperty(target, prop) + } + }) + sm.getBidRequestData(request, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent, timeout); }); function onGetBidRequestDataCallback() { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 6543a6a88e1..477da80d420 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -30,7 +30,7 @@ import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; -const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.2.1.js'; +const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.3.7.js'; // renderer code at https://github.com/rubicon-project/apex2 let rubiConf = config.getConfig('rubicon') || {}; @@ -113,8 +113,10 @@ var sizeMap = { 195: '600x300', 198: '640x360', 199: '640x200', + 210: '1080x1920', 213: '1030x590', 214: '980x360', + 219: '1920x1080', 221: '1x1', 229: '320x180', 230: '2000x1400', @@ -127,7 +129,6 @@ var sizeMap = { 259: '998x200', 261: '480x480', 264: '970x1000', - 265: '1920x1080', 274: '1800x200', 278: '320x500', 282: '320x400', @@ -427,7 +428,6 @@ export const spec = { 'x_source.tid', 'l_pb_bid_id', 'p_screen_res', - 'o_ae', 'o_cdep', 'rp_floor', 'rp_secure', @@ -545,15 +545,11 @@ export const spec = { data['ppuid'] = configUserId; } - if (bidRequest?.ortb2Imp?.ext?.ae) { - data['o_ae'] = 1; - } // If the bid request contains a 'mobile' property under 'ortb2.site', add it to 'data' as 'p_site.mobile'. if (typeof bidRequest?.ortb2?.site?.mobile === 'number') { data['p_site.mobile'] = bidRequest.ortb2.site.mobile } - addDesiredSegtaxes(bidderRequest, data); // loop through userIds and add to request if (bidRequest?.ortb2?.user?.ext?.eids) { bidRequest.ortb2.user.ext.eids.forEach(({ source, uids = [], inserter, matcher, mm, ext = {} }) => { @@ -655,7 +651,7 @@ export const spec = { * @param {*} responseObj * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, * non-SRA responses return a plain BidRequest object - * @return {{fledgeAuctionConfigs: *, bids: *}} An array of bids which + * @return {*} An array of bids */ interpretResponse: function (responseObj, request) { responseObj = responseObj.body; @@ -744,6 +740,13 @@ export const spec = { [bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num)); } + if (ad.bid_cat && ad.bid_cat.length) { + bid.meta.primaryCatId = ad.bid_cat[0]; + if (ad.bid_cat.length > 1) { + bid.meta.secondaryCatIds = ad.bid_cat.slice(1); + } + } + // add server-side targeting bid.rubiconTargeting = (Array.isArray(ad.targeting) ? ad.targeting : []) .reduce((memo, item) => { @@ -760,15 +763,7 @@ export const spec = { return (adB.cpm || 0.0) - (adA.cpm || 0.0); }); - const fledgeAuctionConfigs = responseObj.component_auction_config?.map(config => { - return { config, bidId: config.bidId } - }); - - if (fledgeAuctionConfigs) { - return { bids, paapi: fledgeAuctionConfigs }; - } else { - return bids; - } + return bids; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (syncOptions.iframeEnabled) { @@ -1064,27 +1059,6 @@ function applyFPD(bidRequest, mediaType, data) { } } -function addDesiredSegtaxes(bidderRequest, target) { - if (rubiConf.readTopics === false) { - return; - } - const iSegments = [1, 2, 5, 6, 7, 507].concat(rubiConf.sendSiteSegtax?.map(seg => Number(seg)) || []); - const vSegments = [4, 508].concat(rubiConf.sendUserSegtax?.map(seg => Number(seg)) || []); - const userData = bidderRequest.ortb2?.user?.data || []; - const siteData = bidderRequest.ortb2?.site?.content?.data || []; - userData.forEach(iterateOverSegmentData(target, 'v', vSegments)); - siteData.forEach(iterateOverSegmentData(target, 'i', iSegments)); -} - -function iterateOverSegmentData(target, char, segments) { - return (topic) => { - const taxonomy = Number(topic.ext?.segtax); - if (segments.includes(taxonomy)) { - target[`tg_${char}.tax${taxonomy}`] = topic.segment?.map(seg => seg.id).join(','); - } - } -} - /** * @param sizes * @returns {*} diff --git a/modules/rules/index.ts b/modules/rules/index.ts new file mode 100644 index 00000000000..1bbea07c5d2 --- /dev/null +++ b/modules/rules/index.ts @@ -0,0 +1,506 @@ +import { setLabels } from "../../libraries/analyticsAdapter/AnalyticsAdapter.ts"; +import { timeoutQueue } from "../../libraries/timeoutQueue/timeoutQueue.ts"; +import { ACTIVITY_ADD_BID_RESPONSE, ACTIVITY_FETCH_BIDS } from "../../src/activities/activities.js"; +import { MODULE_TYPE_BIDDER } from "../../src/activities/modules.ts"; +import { ACTIVITY_PARAM_COMPONENT_NAME, ACTIVITY_PARAM_COMPONENT_TYPE } from "../../src/activities/params.js"; +import { registerActivityControl } from "../../src/activities/rules.js"; +import { ajax } from "../../src/ajax.ts"; +import { AuctionIndex } from "../../src/auctionIndex.js"; +import { auctionManager } from "../../src/auctionManager.js"; +import { config } from "../../src/config.ts"; +import { getHook } from "../../src/hook.ts"; +import { generateUUID, logInfo, logWarn } from "../../src/utils.ts"; +import { timedAuctionHook } from "../../src/utils/perfMetrics.ts"; + +/** + * Configuration interface for the shaping rules module. + */ +interface ShapingRulesConfig { + /** + * Endpoint configuration for fetching rules from a remote server. + * If not provided, rules must be provided statically via the `rules` property. + */ + endpoint?: { + /** URL endpoint to fetch rules configuration from */ + url: string; + /** HTTP method to use for fetching rules (currently only 'GET' is supported) */ + method: string; + }; + /** + * Static rules configuration object. + * If provided, rules will be used directly without fetching from endpoint. + * Takes precedence over endpoint configuration. + */ + rules?: RulesConfig; + /** + * Delay in milliseconds to wait for rules to be fetched before starting the auction. + * If rules are not loaded within this delay, the auction will proceed anyway. + * Default: 0 (no delay) + */ + auctionDelay?: number; + /** + * Custom schema evaluator functions to extend the default set of evaluators. + * Keys are function names, values are evaluator functions that take args and context, + * and return a function that evaluates to a value when called. + */ + extraSchemaEvaluators?: { + [key: string]: (args: any[], context: any) => () => any; + }; +} + +/** + * Schema function definition used to compute values. + */ +interface ModelGroupSchema { + /** Function name inside the schema */ + function: string; + /** Arguments for the schema function */ + args?: any[]; +} + +/** + * Model group configuration for A/B testing with different rule configurations. + * Only one object within the group is chosen based on weight. + */ +interface ModelGroup { + /** Determines selection probability; only one object within the group is chosen */ + weight: number; + /** Indicates whether this model group is selected (set automatically based on weight) */ + selected?: boolean; + /** Optional key used to produce aTags, identifying experiments or optimization targets */ + analyticsKey: string; + /** Version identifier for analytics */ + version: string; + /** + * Optional array of functions used to compute values. + * Without it, only the default rule is applied. + */ + schema: ModelGroupSchema[]; + /** + * Optional rule array; if absent, only the default rule is used. + * Each rule has conditions that must be met and results that are triggered. + */ + rules: [{ + /** Conditions that must be met for the rule to apply */ + conditions: string[]; + /** Resulting actions triggered when conditions are met */ + results: [ + { + /** Function defining the result action */ + function: string; + /** Arguments for the result function */ + args: any[]; + } + ]; + }]; + /** + * Default results object used if errors occur or when no schema or rules are defined. + * Exists outside the rules array for structural clarity. + */ + default?: Array<{ + /** Function defining the default result action */ + function: string; + /** Arguments for the default result function */ + args: any; + }>; +} + +/** + * Independent set of rules that can be applied to a specific stage of the auction. + */ +interface RuleSet { + /** Human-readable name of the ruleset */ + name: string; + /** + * Indicates which module stage the ruleset applies to. + * Can be either `processed-auction-request` or `processed-auction` + */ + stage: string; + /** Version identifier for the ruleset */ + version: string; + /** + * Optional timestamp of the last update (ISO 8601 format: `YYYY-MM-DDThh:mm:ss[.sss][Z or ±hh:mm]`) + */ + timestamp?: string; + /** + * One or more model groups for A/B testing with different rule configurations. + * Allows A/B testing with different rule configurations. + */ + modelGroups: ModelGroup[]; +} + +/** + * Main configuration object for the shaping rules module. + */ +interface RulesConfig { + /** Version identifier for the rules configuration */ + version: string; + /** One or more independent sets of rules */ + ruleSets: RuleSet[]; + /** Optional timestamp of the last update (ISO 8601 format: `YYYY-MM-DDThh:mm:ss[.sss][Z or ±hh:mm]`) */ + timestamp?: string; + /** Enables or disables the module. Default: `true` */ + enabled: boolean; +} + +declare module '../../src/config' { + interface Config { + shapingRules?: ShapingRulesConfig; + } +} + +const MODULE_NAME = 'shapingRules'; + +const globalRandomStore = new WeakMap<{ auctionId: string }, number>(); + +let auctionConfigStore = new Map(); + +export const dep = { + getGlobalRandom: getGlobalRandom +}; + +function getGlobalRandom(auctionId: string, auctionIndex: AuctionIndex = auctionManager.index) { + if (!auctionId) { + return Math.random(); + } + const auction = auctionIndex.getAuction({auctionId}); + if (!globalRandomStore.has(auction)) { + globalRandomStore.set(auction, Math.random()); + } + return globalRandomStore.get(auction); +} + +const unregisterFunctions: Array<() => void> = [] + +let moduleConfig: ShapingRulesConfig = { + endpoint: { + method: 'GET', + url: '' + }, + auctionDelay: 0, + extraSchemaEvaluators: {} +}; + +let fetching = false; + +let rulesLoaded = false; + +const delayedAuctions = timeoutQueue(); + +let rulesConfig: RulesConfig = null; + +export function evaluateConfig(config: RulesConfig, auctionId: string) { + if (!config || !config.ruleSets) { + logWarn(`${MODULE_NAME}: Invalid structure for rules engine`); + return; + } + + if (!config.enabled) { + logInfo(`${MODULE_NAME}: Rules engine is disabled in the configuration.`); + return; + } + + const stageRules = config.ruleSets; + + const modelGroupsWithStage = getAssignedModelGroups(stageRules || []); + + for (const { modelGroups, stage } of modelGroupsWithStage) { + const modelGroup = modelGroups.find(group => group.selected); + if (!modelGroup) continue; + evaluateRules(modelGroup.rules || [], modelGroup.schema || [], stage, modelGroup.analyticsKey, auctionId, modelGroup.default); + } +} + +export function getAssignedModelGroups(rulesets: RuleSet[]): Array<{ modelGroups: ModelGroup[], stage: string }> { + return rulesets.flatMap(ruleset => { + const { modelGroups, stage } = ruleset; + if (!modelGroups?.length) { + return []; + } + + // Calculate cumulative weights for proper weighted random selection + let cumulativeWeight = 0; + const groupsWithCumulativeWeights = modelGroups.map(group => { + const groupWeight = group.weight ?? 100; + cumulativeWeight += groupWeight; + return { + group, + cumulativeWeight + }; + }); + + const weightSum = cumulativeWeight; + // Generate random value in range [0, weightSum) + // This ensures each group gets probability proportional to its weight + const randomValue = Math.random() * weightSum; + + // Find first group where cumulative weight >= randomValue + let selectedIndex = groupsWithCumulativeWeights.findIndex(({ cumulativeWeight }) => randomValue < cumulativeWeight); + + // Fallback: if no group was selected (shouldn't happen, but safety check) + if (selectedIndex === -1) { + selectedIndex = modelGroups.length - 1; + } + + // Create new model groups array with selected flag + const newModelGroups = modelGroups.map((group, index) => ({ + ...group, + selected: index === selectedIndex + })); + + return { + modelGroups: newModelGroups, + stage + }; + }); +} + +function evaluateRules(rules, schema, stage, analyticsKey, auctionId: string, defaultResults?) { + const modelGroupConfig = auctionConfigStore.get(auctionId) || []; + modelGroupConfig.push({ + rules, + schema, + stage, + analyticsKey, + defaultResults, + }); + auctionConfigStore.set(auctionId, modelGroupConfig); +} + +const schemaEvaluators = { + percent: (args, context) => () => { + const auctionId = context.auctiondId || context.bid?.auctionId; + return dep.getGlobalRandom(auctionId) * 100 < args[0] + }, + adUnitCode: (args, context) => () => context.adUnit.code, + adUnitCodeIn: (args, context) => () => args[0].includes(context.adUnit.code), + deviceCountry: (args, context) => () => context.ortb2?.device?.geo?.country, + deviceCountryIn: (args, context) => () => args[0].includes(context.ortb2?.device?.geo?.country), + channel: (args, context) => () => 'web', + eidAvailable: (args, context) => () => { + const eids = context.ortb2?.user?.eids || []; + return eids.length > 0; + }, + userFpdAvailable: (args, context) => () => { + const fpd = context.ortb2?.user?.data || {}; + const extFpd = context.ortb2?.user?.ext?.data || {}; + const mergedFpd = { ...fpd, ...extFpd }; + return Object.keys(mergedFpd).length > 0; + }, + fpdAvailable: (args, context) => () => { + const extData = context.ortb2?.user?.ext?.data || {}; + const usrData = context.ortb2?.user?.data || {}; + const siteExtData = context.ortb2?.site?.ext?.data || {}; + const siteContentData = context.ortb2?.site?.content?.data || {}; + const appExtData = context.ortb2?.app?.ext?.data || {}; + const appContentData = context.ortb2?.app?.content?.data || {}; + const mergedFpd = { ...extData, ...usrData, ...siteExtData, ...siteContentData, ...appExtData, ...appContentData }; + return Object.keys(mergedFpd).length > 0; + }, + gppSidIn: (args, context) => () => { + const gppSids = context.ortb2?.regs?.gpp_sid || []; + return args[0].some((sid) => gppSids.includes(sid)); + }, + tcfInScope: (args, context) => () => context.ortb2?.regs?.ext?.gdpr === 1, + domain: (args, context) => () => { + const domain = context.ortb2?.site?.domain || context.ortb2?.app?.domain || ''; + return domain; + }, + domainIn: (args, context) => () => { + const domain = context.ortb2?.site?.domain || context.ortb2?.app?.domain || ''; + return args[0].includes(domain); + }, + bundle: (args, context) => () => { + const bundle = context.ortb2?.app?.bundle || ''; + return bundle; + }, + bundleIn: (args, context) => () => { + const bundle = context.ortb2?.app?.bundle || ''; + return args[0].includes(bundle); + }, + mediaTypeIn: (args, context) => () => { + const mediaTypes = Object.keys(context.adUnit?.mediaTypes) || []; + return args[0].some((type) => mediaTypes.includes(type)); + }, + deviceTypeIn: (args, context) => () => { + const deviceType = context.ortb2?.device?.devicetype; + return args[0].includes(deviceType); + }, + bidPrice: (args, context) => () => { + const [operator, currency, value] = args || []; + const {cpm: bidPrice, currency: bidCurrency} = context.bid || {}; + if (bidCurrency !== currency) { + return false; + } + if (operator === 'gt') { + return bidPrice > value; + } else if (operator === 'gte') { + return bidPrice >= value; + } else if (operator === 'lt') { + return bidPrice < value; + } else if (operator === 'lte') { + return bidPrice <= value; + } + return false; + } +}; + +export function evaluateSchema(func, args, context) { + const extraEvaluators = moduleConfig.extraSchemaEvaluators || {}; + const evaluators = { ...schemaEvaluators, ...extraEvaluators }; + const evaluator = evaluators[func]; + if (evaluator) { + return evaluator(args, context); + } + return () => null; +} + +function evaluateCondition(condition, func) { + switch (condition) { + case '*': + return true + case 'true': + return func() === true; + case 'false': + return func() === false; + default: + return func() === condition; + } +} + +export function fetchRules(endpoint = moduleConfig.endpoint) { + if (fetching) { + logWarn(`${MODULE_NAME}: A fetch is already occurring. Skipping.`); + return; + } + + if (!endpoint?.url || endpoint?.method !== 'GET') return; + + fetching = true; + ajax(endpoint.url, { + success: (response: any) => { + fetching = false; + rulesLoaded = true; + rulesConfig = JSON.parse(response); + delayedAuctions.resume(); + logInfo(`${MODULE_NAME}: Rules configuration fetched successfully.`); + }, + error: () => { + fetching = false; + } + }, null, { method: 'GET' }); +} + +export function registerActivities() { + const stages = { + [ACTIVITY_FETCH_BIDS]: 'processed-auction-request', + [ACTIVITY_ADD_BID_RESPONSE]: 'processed-auction', + }; + + [ACTIVITY_FETCH_BIDS, ACTIVITY_ADD_BID_RESPONSE].forEach(activity => { + unregisterFunctions.push( + registerActivityControl(activity, MODULE_NAME, (params) => { + const auctionId = params.auctionId || params.bid?.auctionId; + if (params[ACTIVITY_PARAM_COMPONENT_TYPE] !== MODULE_TYPE_BIDDER) return; + if (!auctionId) return; + + const checkConditions = ({schema, conditions, stage}) => { + for (const [index, schemaEntry] of schema.entries()) { + const schemaFunction = evaluateSchema(schemaEntry.function, schemaEntry.args || [], params); + if (evaluateCondition(conditions[index], schemaFunction)) { + return true; + } + } + return false; + } + + const results = []; + let modelGroups = auctionConfigStore.get(auctionId) || []; + modelGroups = modelGroups.filter(modelGroup => modelGroup.stage === stages[activity]); + + // evaluate applicable results for each model group + for (const modelGroup of modelGroups) { + // find first rule that matches conditions + const selectedRule = modelGroup.rules.find(rule => checkConditions({...rule, schema: modelGroup.schema})); + if (selectedRule) { + results.push(...selectedRule.results); + } else if (Array.isArray(modelGroup.defaultResults)) { + const defaults = modelGroup.defaultResults.map(result => ({...result, analyticsKey: modelGroup.analyticsKey})); + results.push(...defaults); + } + } + + // set analytics labels for logAtag results + results + .filter(result => result.function === 'logAtag') + .forEach((result) => { + setLabels({ [auctionId + '-' + result.analyticsKey]: result.args.analyticsValue }); + }); + + // verify current bidder against applicable rules + const allow = results + .filter(result => ['excludeBidders', 'includeBidders'].includes(result.function)) + .every((result) => { + return result.args.every(({bidders}) => { + const bidderIncluded = bidders.includes(params[ACTIVITY_PARAM_COMPONENT_NAME]); + return result.function === 'excludeBidders' ? !bidderIncluded : bidderIncluded; + }); + }); + + if (!allow) { + return { allow, reason: `Bidder ${params.bid?.bidder} excluded by rules module` }; + } + }) + ); + }); +} + +export const startAuctionHook = timedAuctionHook('rules', function startAuctionHook(fn, req) { + req.auctionId = req.auctionId || generateUUID(); + evaluateConfig(rulesConfig, req.auctionId); + fn.call(this, req); +}); + +export const requestBidsHook = timedAuctionHook('rules', function requestBidsHook(fn, reqBidsConfigObj) { + const { auctionDelay = 0 } = moduleConfig; + const continueAuction = ((that) => () => fn.call(that, reqBidsConfigObj))(this); + + if (!rulesLoaded && auctionDelay > 0) { + delayedAuctions.submit(auctionDelay, continueAuction, () => { + logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction ${reqBidsConfigObj.auctionId}`) + continueAuction(); + }); + } else { + continueAuction(); + } +}); + +function init(config: ShapingRulesConfig) { + moduleConfig = config; + registerActivities(); + auctionManager.onExpiry(auction => { + auctionConfigStore.delete(auction.getAuctionId()); + }); + // use static config if provided + if (config.rules) { + rulesConfig = config.rules; + } else { + fetchRules(); + } + getHook('requestBids').before(requestBidsHook, 50); + getHook('startAuction').before(startAuctionHook, 50); +} + +export function reset() { + try { + getHook('requestBids').getHooks({hook: requestBidsHook}).remove(); + getHook('startAuction').getHooks({hook: startAuctionHook}).remove(); + unregisterFunctions.forEach(unregister => unregister()); + unregisterFunctions.length = 0; + auctionConfigStore.clear(); + } catch (e) { + } + setLabels({}); +} + +config.getConfig(MODULE_NAME, config => init(config[MODULE_NAME])); diff --git a/modules/scope3RtdProvider.md b/modules/scope3RtdProvider.md index 20dff06c967..f6f63942be3 100644 --- a/modules/scope3RtdProvider.md +++ b/modules/scope3RtdProvider.md @@ -7,6 +7,7 @@ The Scope3 RTD module enables real-time agentic execution for programmatic adver ### What It Does This module: + 1. Captures the **complete OpenRTB request** including all user IDs, geo data, device info, and site context 2. Sends it to Scope3's AEE for real-time analysis 3. Receives back targeting instructions: which line items to include/exclude this impression from @@ -30,34 +31,39 @@ The AEE returns opaque codes (e.g., "x82s") that instruct GAM which line items s pbjs.setConfig({ realTimeData: { auctionDelay: 1000, - dataProviders: [{ - name: 'scope3', - params: { - orgId: 'YOUR_ORG_ID', // Required - your Scope3 organization identifier - - // Optional - customize targeting keys (defaults shown) - includeKey: 's3i', // Key for include segments - excludeKey: 's3x', // Key for exclude segments - macroKey: 's3m', // Key for macro blob - - // Optional - other settings - endpoint: 'https://prebid.scope3.com/prebid', // API endpoint (default) - timeout: 1000, // Milliseconds (default: 1000) - publisherTargeting: true, // Set GAM targeting keys (default: true) - advertiserTargeting: true, // Enrich bid requests (default: true) - bidders: [], // Specific bidders to get data for (empty = all bidders in auction) - cacheEnabled: true, // Enable response caching (default: true) - debugMode: false // Enable debug logging (default: false) - } - }] - } + dataProviders: [ + { + name: "scope3", + waitForIt: true, + params: { + orgId: "YOUR_ORG_ID", // Required - your Scope3 organization identifier + + // Optional - customize targeting keys (defaults shown) + includeKey: "axei", // Key for include segments + excludeKey: "axex", // Key for exclude segments + macroKey: "axem", // Key for macro blob + + // Optional - other settings + endpoint: "https://prebid.scope3.com/prebid", // API endpoint (default) + timeout: 1000, // Milliseconds (default: 1000) + publisherTargeting: true, // Set GAM targeting keys (default: true) + advertiserTargeting: true, // Enrich bid requests (default: true) + bidders: [], // Specific bidders to get data for (empty = all bidders in auction) + cacheEnabled: true, // Enable response caching (default: true) + debugMode: false, // Enable debug logging (default: false) + }, + }, + ], + }, }); ``` ### Advanced Configuration Examples #### Custom Targeting Keys + Use your own naming convention for targeting keys: + ```javascript params: { orgId: 'YOUR_ORG_ID', @@ -68,7 +74,9 @@ params: { ``` #### Specific Bidders Only + Apply AEE signals only to certain bidders: + ```javascript params: { orgId: 'YOUR_ORG_ID', @@ -79,6 +87,7 @@ params: { ``` #### Development/Testing + ```javascript params: { orgId: 'YOUR_ORG_ID', @@ -91,7 +100,9 @@ params: { ## Data Flow ### 1. Complete OpenRTB Capture + The module captures ALL available OpenRTB data: + - **Site**: page URL, domain, referrer, keywords, content, categories - **Device**: user agent, geo location, IP, device type, screen size - **User**: ID, buyer UIDs, year of birth, gender, keywords, data segments, **all extended IDs (eids)** @@ -100,18 +111,20 @@ The module captures ALL available OpenRTB data: - **App**: if in-app, all app details ### 2. Request to AEE + Sends the complete OpenRTB request with list of bidders: + ```json { "orgId": "YOUR_ORG_ID", "ortb2": { - "site": { + "site": { "page": "https://example.com/page", "domain": "example.com", "cat": ["IAB1-1"], "keywords": "news,sports" }, - "device": { + "device": { "ua": "Mozilla/5.0...", "geo": { "country": "USA", @@ -128,7 +141,7 @@ Sends the complete OpenRTB request with list of bidders: "uids": [{"id": "XY123456"}] }, { - "source": "id5-sync.com", + "source": "id5-sync.com", "uids": [{"id": "ID5*abc"}] } ], @@ -144,7 +157,9 @@ Sends the complete OpenRTB request with list of bidders: ``` ### 3. AEE Response + Receives targeting instructions with opaque codes (e.g., 'x82s', 'a91k') that tell GAM which line items to include/exclude. These are NOT audience segments or IAB taxonomy: + ```json { "aee_signals": { @@ -172,13 +187,17 @@ Receives targeting instructions with opaque codes (e.g., 'x82s', 'a91k') that te ### 4. Signal Application #### Publisher Targeting (GAM) + Sets the configured targeting keys (GAM automatically converts to lowercase): -- `s3i` (or your includeKey): ["x82s", "a91k", "p2m7"] - line items to include -- `s3x` (or your excludeKey): ["c4x9", "f7r2"] - line items to exclude -- `s3m` (or your macroKey): "ctx9h3v8s5" - opaque context data + +- `axei` (or your includeKey): ["x82s", "a91k", "p2m7"] - line items to include +- `axex` (or your excludeKey): ["c4x9", "f7r2"] - line items to exclude +- `axem` (or your macroKey): "ctx9h3v8s5" - opaque context data #### Advertiser Data (OpenRTB) + Enriches bid requests with AEE signals: + ```javascript ortb2: { site: { @@ -203,20 +222,22 @@ Create line items that respond to agent targeting instructions. The codes (e.g., ``` Include impression in this line item: -s3i contains "x82s" +axei contains "x82s" Exclude impression from this line item: -s3x does not contain "f7r2" +axex does not contain "f7r2" Multiple targeting conditions: -s3i contains "a91k" AND s3x does not contain "c4x9" +axei contains "a91k" AND axex does not contain "c4x9" Macro data for creative: -s3m is present +axem is present ``` ### Custom Key Configuration + If you use custom keys: + ```javascript // Configuration params: { @@ -239,14 +260,14 @@ Bidders can access AEE signals in their adapters: ```javascript buildRequests: function(validBidRequests, bidderRequest) { const aeeSignals = bidderRequest.ortb2?.site?.ext?.data?.scope3_aee; - + if (aeeSignals) { // Use include segments for targeting payload.targeting_segments = aeeSignals.include; - + // Respect exclude segments payload.exclude_segments = aeeSignals.exclude; - + // Include macro data as opaque string if (aeeSignals.macro) { payload.context_code = aeeSignals.macro; @@ -258,12 +279,15 @@ buildRequests: function(validBidRequests, bidderRequest) { ## Performance Considerations ### Caching + - Responses are cached for 30 seconds by default - Cache key includes: page, user agent, geo, user IDs, and ad units - Reduces redundant API calls for similar contexts ### Data Completeness + The module sends ALL available OpenRTB data to maximize AEE intelligence: + - Extended user IDs (LiveRamp, ID5, UID2, etc.) - Geo location data - Device characteristics @@ -272,6 +296,7 @@ The module sends ALL available OpenRTB data to maximize AEE intelligence: - Regulatory consent status ### Timeout Handling + - Default timeout: 1000ms - Auction continues if AEE doesn't respond in time - No blocking - graceful degradation @@ -287,6 +312,7 @@ The module sends ALL available OpenRTB data to maximize AEE intelligence: ## Troubleshooting ### Enable Debug Mode + ```javascript params: { orgId: 'YOUR_ORG_ID', @@ -297,12 +323,14 @@ params: { ### Common Issues 1. **No signals appearing** + - Verify orgId is correct - Check endpoint is accessible - Ensure timeout is sufficient - Look for console errors in debug mode 2. **Targeting keys not in GAM** + - Verify `publisherTargeting: true` - Check key names match GAM setup - Ensure AEE is returning signals @@ -319,17 +347,21 @@ Minimal setup with defaults: ```javascript pbjs.setConfig({ realTimeData: { - dataProviders: [{ - name: 'scope3', - params: { - orgId: 'YOUR_ORG_ID' // Only required parameter - } - }] - } + dataProviders: [ + { + name: "scope3", + waitForIt: true, + params: { + orgId: "YOUR_ORG_ID", // Only required parameter + }, + }, + ], + }, }); ``` This will: + - Send complete OpenRTB data to Scope3's AEE - Set targeting keys: `s3i` (include), `s3x` (exclude), `s3m` (macro) - Enrich all bidders with AEE signals @@ -338,6 +370,7 @@ This will: ## About the Agentic Execution Engine Scope3's AEE implements the [Ad Context Protocol](https://adcontextprotocol.org) to analyze the complete context of each bid opportunity. By processing the full OpenRTB request including all user IDs, geo data, and site context, the AEE can: + - Identify optimal audience segments in real-time - Detect and prevent unwanted targeting scenarios - Apply complex business rules at scale @@ -346,6 +379,7 @@ Scope3's AEE implements the [Ad Context Protocol](https://adcontextprotocol.org) ## Support For technical support and AEE configuration: + - Documentation: https://docs.scope3.com - Ad Context Protocol: https://adcontextprotocol.org -- Support: support@scope3.com \ No newline at end of file +- Support: support@scope3.com diff --git a/modules/scope3RtdProvider.ts b/modules/scope3RtdProvider.ts index 620ae36108c..798a638559b 100644 --- a/modules/scope3RtdProvider.ts +++ b/modules/scope3RtdProvider.ts @@ -112,9 +112,9 @@ function initModule(config: any): boolean { // Set defaults moduleConfig.endpoint = moduleConfig.endpoint || DEFAULT_ENDPOINT; moduleConfig.timeout = moduleConfig.timeout || DEFAULT_TIMEOUT; - moduleConfig.includeKey = moduleConfig.includeKey || 'scope3_include'; - moduleConfig.excludeKey = moduleConfig.excludeKey || 'scope3_exclude'; - moduleConfig.macroKey = moduleConfig.macroKey || 'scope3_macro'; + moduleConfig.includeKey = moduleConfig.includeKey || 'axei'; + moduleConfig.excludeKey = moduleConfig.excludeKey || 'axex'; + moduleConfig.macroKey = moduleConfig.macroKey || 'axem'; moduleConfig.publisherTargeting = moduleConfig.publisherTargeting !== false; moduleConfig.advertiserTargeting = moduleConfig.advertiserTargeting !== false; moduleConfig.cacheEnabled = moduleConfig.cacheEnabled !== false; diff --git a/modules/screencoreBidAdapter.js b/modules/screencoreBidAdapter.js index ac6f5895751..ccf59b28ce7 100644 --- a/modules/screencoreBidAdapter.js +++ b/modules/screencoreBidAdapter.js @@ -1,16 +1,18 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; import { - createBuildRequestsFn, - createInterpretResponseFn, - createUserSyncGetter, isBidRequestValid, -} from '../libraries/vidazooUtils/bidderUtils.js'; + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction +} from '../libraries/teqblazeUtils/bidderUtils.js'; +import { getTimeZone } from '../libraries/timezone/timezone.js'; const BIDDER_CODE = 'screencore'; const GVLID = 1473; const BIDDER_VERSION = '1.0.0'; +const SYNC_URL = 'https://cs.screencore.io'; const REGION_SUBDOMAIN_SUFFIX = { EU: 'taqeu', US: 'taqus', @@ -23,8 +25,7 @@ const REGION_SUBDOMAIN_SUFFIX = { */ function getRegionSubdomainSuffix() { try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; + const region = getTimeZone().split('/')[0]; switch (region) { case 'Asia': @@ -48,32 +49,29 @@ function getRegionSubdomainSuffix() { } } -export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); - export function createDomain() { const subDomain = getRegionSubdomainSuffix(); return `https://${subDomain}.screencore.io`; } -const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); +const AD_URL = `${createDomain()}/prebid`; -const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const placementProcessingFunction = buildPlacementProcessingFunction(); -const getUserSyncs = createUserSyncGetter({ - iframeSyncUrl: 'https://cs.screencore.io/api/sync/iframe', - imageSyncUrl: 'https://cs.screencore.io/api/sync/image', -}); +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid, + isBidRequestValid: isBidRequestValid(), buildRequests, interpretResponse, - getUserSyncs, + getUserSyncs: getUserSyncs(SYNC_URL), }; registerBidder(spec); diff --git a/modules/screencoreBidAdapter.md b/modules/screencoreBidAdapter.md index 60dc9b9ab21..8e5d9e3d3da 100644 --- a/modules/screencoreBidAdapter.md +++ b/modules/screencoreBidAdapter.md @@ -27,7 +27,8 @@ var adUnits = [ param1: 'loremipsum', param2: 'dolorsitamet' }, - placementId: 'testBanner' + placementId: 'testBanner', + endpointId: 'testEndpoint' } } ] diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index a6dfa0076d7..f93ed814a0a 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -4,6 +4,7 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { _map, getWinDimensions, isArray, triggerPixel } from '../src/utils.js'; import { getViewportCoordinates } from '../libraries/viewport/viewport.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -53,12 +54,9 @@ function getBidFloor(bidRequest) { } const getConnectionType = () => { - const connection = - navigator.connection || - navigator.mozConnection || - navigator.webkitConnection || - {}; - switch (connection.type || connection.effectiveType) { + const connection = getConnectionInfo(); + const connectionType = connection?.type || connection?.effectiveType; + switch (connectionType) { case 'wifi': case 'ethernet': return deviceConnection.FIXED; @@ -127,7 +125,7 @@ function buildBidRequest(validBidRequest) { adUnitCode: validBidRequest.adUnitCode, geom: geom(validBidRequest.adUnitCode), placement: params.placement, - requestCount: validBidRequest.bidRequestsCount || 1, + requestCount: validBidRequest.bidderRequestsCount || 1, }; if (hasVideoMediaType(validBidRequest) && hasMandatoryVideoParams(validBidRequest)) { diff --git a/modules/seenthisBrandStories.md b/modules/seenthisBrandStories.md new file mode 100644 index 00000000000..8b438d8ca6c --- /dev/null +++ b/modules/seenthisBrandStories.md @@ -0,0 +1,10 @@ +# Overview + +Module Name: SeenThis Brand Stories +Maintainer: tech@seenthis.se + +# Description + +Module to allow publishers to handle SeenThis Brand Stories ads. The module will handle communication with the ad iframe and resize the ad iframe accurately and handle fullscreen mode according to product specification. + +This will allow publishers to safely run the ad format without the need to disable Safeframe when using Prebid.js. diff --git a/modules/seenthisBrandStories.ts b/modules/seenthisBrandStories.ts new file mode 100644 index 00000000000..8c6ee589272 --- /dev/null +++ b/modules/seenthisBrandStories.ts @@ -0,0 +1,161 @@ +import { EVENTS } from "../src/constants.js"; +import { getBoundingClientRect } from "../libraries/boundingClientRect/boundingClientRect.js"; +import { getWinDimensions } from "../src/utils.js"; +import * as events from "../src/events.js"; + +export const DEFAULT_MARGINS = "16px"; +export const SEENTHIS_EVENTS = [ + "@seenthis_storylines/ready", + "@seenthis_enabled", + "@seenthis_disabled", + "@seenthis_metric", + "@seenthis_detach", + "@seenthis_modal/opened", + "@seenthis_modal/closed", + "@seenthis_modal/beforeopen", + "@seenthis_modal/beforeclose", +]; + +const classNames: Record = { + container: "storylines-container", + expandedBody: "seenthis-storylines-fullscreen", +}; +const containerElements: Record = {}; +const frameElements: Record = {}; +const isInitialized: Record = {}; + +export function calculateMargins(element: HTMLElement) { + const boundingClientRect = getBoundingClientRect(element); + const wrapperLeftMargin = window.getComputedStyle(element).marginLeft; + const marginLeft = boundingClientRect.left - parseInt(wrapperLeftMargin, 10); + + if (boundingClientRect.width === 0 || marginLeft === 0) { + element.style.setProperty("--storylines-margins", DEFAULT_MARGINS); + element.style.setProperty("--storylines-margin-left", DEFAULT_MARGINS); + return; + } + element.style.setProperty("--storylines-margin-left", `-${marginLeft}px`); + element.style.setProperty("--storylines-margins", `${marginLeft * 2}px`); +} + +export function getFrameByEvent(event: MessageEvent) { + const isAncestor = (childWindow: Window, frameWindow: Window) => { + if (frameWindow === childWindow) { + return true; + } else if (childWindow === window.top) { + return false; + } + if (!childWindow?.parent) return false; + return isAncestor(childWindow.parent, frameWindow); + }; + const iframeThatMatchesSource = Array.from( + document.getElementsByTagName("iframe") + ).find((frame) => + isAncestor(event.source as Window, frame.contentWindow as Window) + ); + return iframeThatMatchesSource || null; +} + +export function addStyleToSingleChildAncestors( + element: HTMLElement, + { key, value }: { key: string; value: string } +) { + if (!element || !key) return; + if ( + key in element.style && + "offsetWidth" in element && + element.offsetWidth < getWinDimensions().innerWidth + ) { + element.style.setProperty(key, value); + } + if (!element.parentElement || element.parentElement?.children.length > 1) { + return; + } + addStyleToSingleChildAncestors(element.parentElement, { key, value }); +} + +export function findAdWrapper(target: HTMLDivElement) { + return target?.parentElement?.parentElement; +} + +export function applyFullWidth(target: HTMLDivElement) { + const adWrapper = findAdWrapper(target); + if (adWrapper) { + addStyleToSingleChildAncestors(adWrapper, { key: "width", value: "100%" }); + } +} + +export function applyAutoHeight(target: HTMLDivElement) { + const adWrapper = findAdWrapper(target); + if (adWrapper) { + addStyleToSingleChildAncestors(adWrapper, { key: "height", value: "auto" }); + addStyleToSingleChildAncestors(adWrapper, { + key: "min-height", + value: "auto", + }); + } +} + +// listen to messages from iframes +window.addEventListener("message", (event) => { + if (!["https://video.seenthis.se"].includes(event?.origin)) return; + + const data = event?.data; + if (!data) return; + + switch (data.type) { + case "storylines:init": { + const storyKey = data.storyKey; + if (!storyKey || isInitialized[storyKey]) return; + isInitialized[storyKey] = true; + + frameElements[storyKey] = getFrameByEvent(event); + containerElements[storyKey] = frameElements[storyKey] + ?.parentElement as HTMLDivElement; + event.source?.postMessage( + "storylines:init-ok", + "*" as WindowPostMessageOptions + ); + + const styleEl = document.createElement("style"); + styleEl.textContent = data.css; + document.head.appendChild(styleEl); + if (data.fixes.includes("full-width")) { + applyFullWidth(containerElements[storyKey]); + } + if (data.fixes.includes("auto-height")) { + applyAutoHeight(containerElements[storyKey]); + } + + containerElements[storyKey]?.classList.add(classNames.container); + calculateMargins(containerElements[storyKey]); + + events.emit(EVENTS.BILLABLE_EVENT, { + vendor: "seenthis", + type: "storylines_init", + }); + break; + } + case "@seenthis_modal/beforeopen": { + const storyKey = data.detail.storyKey; + document.body.classList.add(classNames.expandedBody); + containerElements[storyKey]?.classList.add("expanded"); + break; + } + case "@seenthis_modal/beforeclose": { + const storyKey = data.detail.storyKey; + document.body.classList.remove(classNames.expandedBody); + containerElements[storyKey]?.classList.remove("expanded"); + break; + } + } + + // dispatch SEENTHIS_EVENTS to parent window + if (SEENTHIS_EVENTS.includes(data.type)) { + window.dispatchEvent(new CustomEvent(data.type, { detail: data })); + } +}); + +Array.from(window.frames).forEach((frame) => { + frame.postMessage("storylines:bridge-ready", "*"); +}); diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index 4551bd2c76c..b5db12b1cf5 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -7,7 +7,7 @@ import {getDomComplexity, getPageDescription, getPageTitle} from "../libraries/f import * as converter from '../libraries/ortbConverter/converter.js'; const PREBID_VERSION = '$prebid.version$'; -const ADAPTER_VERSION = '1.0'; +const ADAPTER_VERSION = '1.0.1'; const ORTB = converter.ortbConverter({ context: { ttl: 300 } }); @@ -25,6 +25,57 @@ const getReferrerInfo = (bidderRequest) => { return bidderRequest?.refererInfo?.page ?? ''; } +const normalizeKeywords = (input) => { + if (!input) return []; + + if (Array.isArray(input)) { + return input.map(k => k.trim()).filter(Boolean); + } + + if (typeof input === 'string') { + return input + .split(',') + .map(k => k.trim()) + .filter(Boolean); + } + + // Any other type → ignore + return []; +}; + +function resolveDataType(asset) { + if (typeof asset?.data?.type === 'number') { + return asset.data.type; + } + + if (typeof asset?.id === 'number') { + return asset.id; + } + + return null; +} + +// Helper: resolve the "image type" for an asset +// Returns 1 (icon), 3 (image) or null if unknown +function resolveImageType(asset) { + if (!asset) return null; + + // 1) explicit image type in the img block (preferred) + if (typeof asset.img?.type === 'number') return asset.img.type; + + // 2) fallback to data.type (some bidders put the type here) + if (typeof asset.data?.type === 'number') return asset.data.type; + + // 3) last resort: map legacy asset.id values to image types + // (13 -> icon, 14 -> image) — keep this mapping isolated here + if (typeof asset.id === 'number') { + if (asset.id === 13) return 1; // icon + if (asset.id === 14) return 3; // image + } + + return null; +} + const parseNativeAd = function (bid) { try { const nativeAd = JSON.parse(bid.ad); @@ -36,7 +87,8 @@ const parseNativeAd = function (bid) { } if (asset.data) { const value = asset.data.value; - switch (asset.data.type) { + const type = resolveDataType(asset); + switch (type) { case 1: if (value) native.sponsored = value; break; case 2: if (value) native.desc = value; break; case 3: if (value) native.rating = value; break; @@ -53,13 +105,14 @@ const parseNativeAd = function (bid) { } } if (asset.img) { - const { url, w = 0, h = 0, type } = asset.img; + const { url, w = 0, h = 0 } = asset.img; + const imgType = resolveImageType(asset); - if (type === 1 && url) { + if (imgType === 1 && url) { native.icon = url; native.icon_width = w; native.icon_height = h; - } else if (type === 3 && url) { + } else if (imgType === 3 && url) { native.image = url; native.image_width = w; native.image_height = h; @@ -131,6 +184,11 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const userSyncEnabled = config.getConfig("userSync.syncEnabled"); + const currencyConfig = config.getConfig('currency'); + const currency = + currencyConfig?.adServerCurrency || + currencyConfig?.defaultCurrency || + null; // (!) that avoids top-level side effects (the thing that can stop registerBidder from running) const computeTTFB = (w = (typeof window !== 'undefined' ? window : undefined)) => { try { @@ -212,6 +270,7 @@ export const spec = { source: eid.source, id: eid.uids?.[0]?.id })).filter(eid => eid.source && eid.id), + ...(currency ? { currency } : {}), ads: [ { sizes: formattedSizes, @@ -221,7 +280,12 @@ export const spec = { ...(isNative && { nativeRequest: { ver: "1.2", assets: processedAssets || {}} }) }, ], - keywords: { tokens: ortbRequest?.site?.keywords || bidRequest.params?.keywords || [] }, + keywords: { + tokens: normalizeKeywords( + ortbRequest?.site?.keywords || + bidRequest.params?.keywords + ) + }, privacy: { gpp: gpp?.consentString || "", tcfeu: gdpr?.consentString || "", @@ -233,8 +297,13 @@ export const spec = { userSyncOption: userSyncEnabled === false ? "OFF" : "BIDDERS", referer: getReferrerInfo(bidderRequest), pageReferer: document.referrer, - pageTitle: getPageTitle().slice(0, 300), - pageDescription: getPageDescription().slice(0, 300), + context: [{ + source: "title", + text: getPageTitle().slice(0, 300) + }, { + source: "meta:description", + text: getPageDescription().slice(0, 300) + }], domComplexity: getDomComplexity(document), device: bidderRequest?.ortb2?.device || {}, deviceWidth: screen.width, diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 4d84c21a99e..a11820b5897 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -116,10 +116,6 @@ export const sharethroughAdapterSpec = { } } - if (bidderRequest.ortb2?.device?.ext?.cdep) { - 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); diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 9565f318ead..2a896a92499 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -27,12 +27,14 @@ const ALIASES = { 'anzu': {area: '1', pid: '445'}, 'amcom': {area: '1', pid: '397'}, 'adastra': {area: '1', pid: '33'}, + 'radiantfusion': {area: '1', pid: '455'}, }; const BASE_URLS = { 'attekmi': 'https://prebid.attekmi.co/pbjs', 'smarthub': 'https://prebid.attekmi.co/pbjs', 'markapp': 'https://markapp-prebid.attekmi.co/pbjs', + 'markapp-apac': 'https://markapp-apac-prebid.attekmi.co/pbjs', 'jdpmedia': 'https://jdpmedia-prebid.attekmi.co/pbjs', 'tredio': 'https://tredio-prebid.attekmi.co/pbjs', 'felixads': 'https://felixads-prebid.attekmi.co/pbjs', @@ -44,20 +46,21 @@ const BASE_URLS = { 'anzu': 'https://anzu-prebid.attekmi.co/pbjs', 'amcom': 'https://amcom-prebid.attekmi.co/pbjs', 'adastra': 'https://adastra-prebid.attekmi.co/pbjs', + 'radiantfusion': 'https://radiantfusion-prebid.attekmi.co/pbjs', }; const adapterState = {}; const _getPartnerUrl = (partner) => { const region = ALIASES[partner]?.region; - const partnerRegion = region ? `${partner}-${String(region).toLocaleLowerCase()}` : partner; + const partnerName = region ? `${partner}-${String(region).toLocaleLowerCase()}` : partner; const urls = Object.keys(BASE_URLS); - if (urls.includes(partnerRegion)) { - return BASE_URLS[partnerRegion]; + if (urls.includes(partnerName)) { + return BASE_URLS[partnerName]; } - return `${BASE_URLS[BIDDER_CODE]}?partnerName=${partnerRegion}`; + return `${BASE_URLS[BIDDER_CODE]}?partnerName=${partnerName}`; } const _getPartnerName = (bid) => String(bid.params?.partnerName || bid.bidder).toLowerCase(); diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 7e4dede2ce6..1b337fd30cf 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -8,7 +8,6 @@ import { isInteger, logWarn, getBidIdParameter, - isEmptyStr, mergeDeep } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' @@ -40,6 +39,7 @@ export const spec = { code: 'sovrn', supportedMediaTypes: [BANNER, VIDEO], gvlid: 13, + alwaysHasCapacity: true, /** * Check if the bid is a valid zone ID in either number or string form @@ -122,17 +122,6 @@ export const spec = { imp.ext = imp.ext || {} imp.ext.deals = segmentsString.split(',').map(deal => deal.trim()) } - - const auctionEnvironment = bid?.ortb2Imp?.ext?.ae - if (bidderRequest.paapi?.enabled && isInteger(auctionEnvironment)) { - imp.ext = imp.ext || {} - imp.ext.ae = auctionEnvironment - } else { - if (imp.ext?.ae) { - delete imp.ext.ae - } - } - sovrnImps.push(imp) }) @@ -217,13 +206,13 @@ export const spec = { /** * Format Sovrn responses as Prebid bid responses * @param {*} param0 A successful response from Sovrn. - * @return {Array} An array of formatted bids (+ fledgeAuctionConfigs if available) + * @return {Bid[]} An array of formatted bids. */ - interpretResponse: function({ body: {id, seatbid, ext} }) { + interpretResponse: function({ body: {id, seatbid} }) { if (!id || !seatbid || !Array.isArray(seatbid)) return [] try { - const bids = seatbid + return seatbid .filter(seat => seat) .map(seat => seat.bid.map(sovrnBid => { const bid = { @@ -249,45 +238,6 @@ export const spec = { return bid })) .flat() - - let fledgeAuctionConfigs = null; - if (isArray(ext?.igbid)) { - const seller = ext.seller - const decisionLogicUrl = ext.decisionLogicUrl - const sellerTimeout = ext.sellerTimeout - ext.igbid.filter(item => isValidIgBid(item)).forEach((igbid) => { - const perBuyerSignals = {} - igbid.igbuyer.filter(item => isValidIgBuyer(item)).forEach(buyerItem => { - perBuyerSignals[buyerItem.igdomain] = buyerItem.buyerdata - }) - const interestGroupBuyers = [...Object.keys(perBuyerSignals)] - if (interestGroupBuyers.length) { - fledgeAuctionConfigs = fledgeAuctionConfigs || {} - fledgeAuctionConfigs[igbid.impid] = { - seller, - decisionLogicUrl, - sellerTimeout, - interestGroupBuyers: interestGroupBuyers, - perBuyerSignals, - } - } - }) - } - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return { - bidId, - config: Object.assign({ - auctionSignals: {} - }, cfg) - } - }) - return { - bids, - paapi: fledgeAuctionConfigs, - } - } - return bids } catch (e) { logError('Could not interpret bidresponse, error details:', e) return e @@ -385,12 +335,4 @@ function _getBidFloors(bid) { return !isNaN(paramValue) ? paramValue : undefined } -function isValidIgBid(igBid) { - return !isEmptyStr(igBid.impid) && isArray(igBid.igbuyer) && igBid.igbuyer.length -} - -function isValidIgBuyer(igBuyer) { - return !isEmptyStr(igBuyer.igdomain) -} - registerBidder(spec) diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 3625b912579..5ce8fb34492 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,4 +1,5 @@ import { deepAccess, getWinDimensions, getWindowTop, isArray, logInfo, logWarn } from '../src/utils.js'; +import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -165,7 +166,7 @@ const getNotificationPayload = bidData => { const applyClientHints = ortbRequest => { const { location } = document; - const { connection = {}, deviceMemory, userAgentData = {} } = navigator; + const { connection = {}, userAgentData = {} } = navigator; const viewport = getWinDimensions().visualViewport || false; const segments = []; const hints = { @@ -173,8 +174,8 @@ const applyClientHints = ortbRequest => { 'CH-Rtt': connection.rtt, 'CH-SaveData': connection.saveData, 'CH-Downlink': connection.downlink, - 'CH-DeviceMemory': deviceMemory, - 'CH-Dpr': W.devicePixelRatio, + 'CH-DeviceMemory': null, + 'CH-Dpr': getDevicePixelRatio(W), 'CH-ViewportWidth': viewport.width, 'CH-BrowserBrands': JSON.stringify(userAgentData.brands), 'CH-isMobile': userAgentData.mobile, diff --git a/modules/startioBidAdapter.js b/modules/startioBidAdapter.js index 76a68f8ce95..74629f2cc9c 100644 --- a/modules/startioBidAdapter.js +++ b/modules/startioBidAdapter.js @@ -7,7 +7,7 @@ import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; const BIDDER_CODE = 'startio'; const METHOD = 'POST'; const GVLID = 1216; -const ENDPOINT_URL = `http://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`; +const ENDPOINT_URL = `https://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`; const converter = ortbConverter({ imp(buildImp, bidRequest, context) { diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index dda4694a52c..421ddbd256b 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,10 +3,14 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject} from '../src/utils.js'; +import {deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject, getWinDimensions} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {getConnectionType} from '../libraries/connectionInfo/connectionUtils.js'; +import {getViewportCoordinates} from '../libraries/viewport/viewport.js'; +import {percentInView} from '../libraries/percentInView/percentInView.js'; +import {getBoundingClientRect} from '../libraries/boundingClientRect/boundingClientRect.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -95,6 +99,73 @@ export const internal = { } } +export function detectBot() { + try { + return { + detected: !!( + window.__nightmare || + window.callPhantom || + window._phantom || + /HeadlessChrome/.test(navigator.userAgent) + ) + }; + } catch (e) { + return { detected: false }; + } +} + +export function getPageVisibility() { + try { + return { + hidden: document.hidden, + state: document.visibilityState, + hasFocus: document.hasFocus() + }; + } catch (e) { + return { hidden: false, state: 'visible', hasFocus: true }; + } +} + +export function getDeviceExtSignals(existingExt = {}) { + const viewport = getViewportCoordinates(); + return { + ...existingExt, + bot: detectBot(), + visibility: getPageVisibility(), + scroll: { + top: Math.round(viewport.top), + left: Math.round(viewport.left) + } + }; +} + +export function getElementSignals(adUnitCode) { + try { + const element = document.getElementById(adUnitCode); + if (!element) return null; + + const rect = getBoundingClientRect(element); + const winDimensions = getWinDimensions(); + const rawViewability = percentInView(element); + + const signals = { + placement: { + top: Math.round(rect.top), + left: Math.round(rect.left) + }, + fold: rect.top < winDimensions.innerHeight ? 'above' : 'below' + }; + + if (rawViewability !== null && !isNaN(rawViewability)) { + signals.viewability = Math.round(rawViewability); + } + + return signals; + } catch (e) { + return null; + } +} + const converter = ortbConverter({ context: { netRevenue: true, @@ -108,12 +179,15 @@ const converter = ortbConverter({ }, request(buildRequest, imps, bidderRequest, context) { const reqData = buildRequest(imps, bidderRequest, context); - fillTaboolaReqData(bidderRequest, context.bidRequests[0], reqData) + fillTaboolaReqData(bidderRequest, context.bidRequests[0], reqData, context); return reqData; }, bidResponse(buildBidResponse, bid, context) { const bidResponse = buildBidResponse(bid, context); bidResponse.nurl = bid.nurl; + if (bid.burl) { + bidResponse.burl = bid.burl; + } bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); if (bid.ext && bid.ext.dchain) { deepSetValue(bidResponse, 'meta.dchain', bid.ext.dchain); @@ -134,7 +208,12 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; - const data = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); + const auctionId = bidderRequest.auctionId || validBidRequests[0]?.auctionId; + const data = converter.toORTB({ + bidderRequest: bidderRequest, + bidRequests: validBidRequests, + context: { auctionId } + }); const {publisherId} = bidRequest.params; const url = END_POINT_URL + '?publisher=' + publisherId; @@ -212,9 +291,20 @@ export const spec = { return bids; }, onBidWon: (bid) => { - if (bid.nurl) { + if (bid.nurl && !bid.deferBilling) { const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.originalCpm); ajax(resolvedNurl); + bid.taboolaBillingFired = true; + } + }, + onBidBillable: (bid) => { + if (bid.taboolaBillingFired) { + return; + } + const billingUrl = bid.burl || bid.nurl; + if (billingUrl) { + const resolvedBillingUrl = replaceAuctionPrice(billingUrl, bid.originalCpm); + ajax(resolvedBillingUrl); } }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { @@ -273,10 +363,19 @@ function getSiteProperties({publisherId}, refererInfo, ortb2) { } } -function fillTaboolaReqData(bidderRequest, bidRequest, data) { +function fillTaboolaReqData(bidderRequest, bidRequest, data, context) { const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); - deepSetValue(data, 'device', bidderRequest?.ortb2?.device); + + const ortb2Device = bidderRequest?.ortb2?.device || {}; + const connectionType = getConnectionType(); + const device = { + ...ortb2Device, + js: 1, + ...(connectionType && { connectiontype: connectionType }), + ext: getDeviceExtSignals(ortb2Device.ext) + }; + deepSetValue(data, 'device', device); const extractedUserId = userData.getUserId(gdprConsent, uspConsent); if (data.user === undefined || data.user === null) { data.user = { @@ -326,6 +425,10 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data) { data.wlang = ortb2.wlang || bidRequest.params.wlang || []; deepSetValue(data, 'ext.pageType', ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType); deepSetValue(data, 'ext.prebid.version', '$prebid.version$'); + const auctionId = context?.auctionId; + if (auctionId) { + deepSetValue(data, 'ext.prebid.auctionId', auctionId); + } } function fillTaboolaImpData(bid, imp) { @@ -348,6 +451,29 @@ function fillTaboolaImpData(bid, imp) { imp.bidfloorcur = bidfloorcur; } deepSetValue(imp, 'ext.gpid', bid?.ortb2Imp?.ext?.gpid); + + if (bid.bidId) { + deepSetValue(imp, 'ext.prebid.bidId', bid.bidId); + } + if (bid.adUnitCode) { + deepSetValue(imp, 'ext.prebid.adUnitCode', bid.adUnitCode); + } + if (bid.adUnitId) { + deepSetValue(imp, 'ext.prebid.adUnitId', bid.adUnitId); + } + + deepSetValue(imp, 'ext.prebid.bidRequestsCount', bid.bidRequestsCount); + deepSetValue(imp, 'ext.prebid.bidderRequestsCount', bid.bidderRequestsCount); + deepSetValue(imp, 'ext.prebid.bidderWinsCount', bid.bidderWinsCount); + + const elementSignals = getElementSignals(bid.adUnitCode); + if (elementSignals) { + if (elementSignals.viewability !== undefined) { + deepSetValue(imp, 'ext.viewability', elementSignals.viewability); + } + deepSetValue(imp, 'ext.placement', elementSignals.placement); + deepSetValue(imp, 'ext.fold', elementSignals.fold); + } } function getBanners(bid, pos) { diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index 84730231543..723c9d77dbd 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -1,4 +1,4 @@ -import {_each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; +import {_each, deepAccess, getDefinedParams, isFn, isPlainObject, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {formatRequest, getRtbBid, getSiteObj, getSyncResponse, videoBid, bannerBid, createVideoTag} from '../libraries/targetVideoUtils/bidderUtils.js'; @@ -9,6 +9,22 @@ import {SOURCE, GVLID, BIDDER_CODE, VIDEO_PARAMS, BANNER_ENDPOINT_URL, VIDEO_END * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.floor) ? bid.params.floor : null; + } + + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { + return floor.floor; + } + return null; +} + export const spec = { code: BIDDER_CODE, @@ -38,13 +54,15 @@ export const spec = { version: '$prebid.version$' }; - for (let {params, bidId, sizes, mediaTypes, ...bid} of bidRequests) { + for (let {bidId, sizes, mediaTypes, ...bid} of bidRequests) { for (const mediaType in mediaTypes) { switch (mediaType) { case VIDEO: { + const params = bid.params; const video = mediaTypes[VIDEO]; const placementId = params.placementId; const site = getSiteObj(); + const floor = getBidFloor(bid); if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; @@ -71,6 +89,12 @@ export const spec = { video: getDefinedParams(video, VIDEO_PARAMS) } + const bidFloor = typeof floor === 'string' ? Number(floor.trim()) + : typeof floor === 'number' ? floor + : NaN; + + if (Number.isFinite(bidFloor) && bidFloor > 0) imp.bidfloor = bidFloor; + if (video.playerSize) { imp.video = Object.assign( imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {} diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md index a34ad0aff27..9a204a86991 100644 --- a/modules/targetVideoBidAdapter.md +++ b/modules/targetVideoBidAdapter.md @@ -43,6 +43,7 @@ var adUnits = [ bidder: 'targetVideo', params: { placementId: 12345, + floor: 2, reserve: 0, } }] diff --git a/modules/tcfControl.ts b/modules/tcfControl.ts index 6884c5a96cc..81d5df3d802 100644 --- a/modules/tcfControl.ts +++ b/modules/tcfControl.ts @@ -65,7 +65,8 @@ const CONFIGURABLE_RULES = { purpose: 'basicAds', enforcePurpose: true, enforceVendor: true, - vendorExceptions: [] + vendorExceptions: [], + deferS2Sbidders: false } }, personalizedAds: { @@ -228,7 +229,7 @@ function getConsent(consentData, type, purposeNo, gvlId) { * @param {number=} gvlId - GVL ID for the module * @returns {boolean} */ -export function validateRules(rule, consentData, currentModule, gvlId) { +export function validateRules(rule, consentData, currentModule, gvlId, params = {}) { const ruleOptions = CONFIGURABLE_RULES[rule.purpose]; // return 'true' if vendor present in 'vendorExceptions' @@ -236,8 +237,9 @@ export function validateRules(rule, consentData, currentModule, gvlId) { return true; } const vendorConsentRequred = rule.enforceVendor && !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))); + const deferS2Sbidders = params['isS2S'] && rule.purpose === 'basicAds' && rule.deferS2Sbidders && !gvlId; const {purpose, vendor} = getConsent(consentData, ruleOptions.type, ruleOptions.id, gvlId); - return (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); + return (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || deferS2Sbidders || vendor); } function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback: any = () => null) { @@ -247,7 +249,7 @@ function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback: any = if (shouldEnforce(consentData, purposeNo, modName)) { const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params)); - const allow = !!checkConsent(consentData, modName, gvlid); + const allow = !!checkConsent(consentData, modName, gvlid, params); if (!allow) { blocked && blocked.add(modName); return {allow}; @@ -257,7 +259,7 @@ function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback: any = } function singlePurposeGdprRule(purposeNo, blocked = null, gvlidFallback: any = () => null) { - return gdprRule(purposeNo, (cd, modName, gvlid) => !!validateRules(ACTIVE_RULES.purpose[purposeNo], cd, modName, gvlid), blocked, gvlidFallback); + return gdprRule(purposeNo, (cd, modName, gvlid, params) => !!validateRules(ACTIVE_RULES.purpose[purposeNo], cd, modName, gvlid, params), blocked, gvlidFallback); } function exceptPrebidModules(ruleFn) { diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 8d12bf81a3e..dbdda501658 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,9 +1,11 @@ import {logError, parseSizesInput, isArray, getBidIdParameter, getWinDimensions, getScreenOrientation} from '../src/utils.js'; +import {getDevicePixelRatio} from '../libraries/devicePixelRatio/devicePixelRatio.js'; 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'; +import {getHLen} from '../libraries/navigatorData/navigatorData.js'; import {getTimeToFirstByte} from '../libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -64,21 +66,21 @@ export const spec = { pageReferrer: document.referrer, pageTitle: getPageTitle().slice(0, 300), pageDescription: getPageDescription().slice(0, 300), - networkBandwidth: getConnectionDownLink(window.navigator), - networkQuality: getNetworkQuality(window.navigator), + networkBandwidth: getConnectionDownLink(), + networkQuality: getNetworkQuality(), timeToFirstByte: getTimeToFirstByte(window), data: bids, domComplexity: getDomComplexity(document), device: bidderRequest?.ortb2?.device || {}, deviceWidth: screen.width, deviceHeight: screen.height, - devicePixelRatio: topWindow.devicePixelRatio, + devicePixelRatio: getDevicePixelRatio(topWindow), screenOrientation: getScreenOrientation(), historyLength: getHLen(), viewportHeight: getWinDimensions().visualViewport.height, viewportWidth: getWinDimensions().visualViewport.width, - hardwareConcurrency: getHC(), - deviceMemory: getDM(), + hardwareConcurrency: null, + deviceMemory: null, hb_version: '$prebid.version$', timeout: bidderRequest?.timeout, eids: getUserIdAsEids(validBidRequests), @@ -256,12 +258,13 @@ function getPageDescription() { return (element && element.content) || ''; } -function getConnectionDownLink(nav) { - return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : ''; +function getConnectionDownLink() { + const connection = getConnectionInfo(); + return connection?.downlink != null ? connection.downlink.toString() : ''; } -function getNetworkQuality(navigator) { - const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; +function getNetworkQuality() { + const connection = getConnectionInfo(); return connection?.effectiveType ?? ''; } diff --git a/modules/tealBidAdapter.js b/modules/tealBidAdapter.js index 4374646b102..f9175ffb564 100644 --- a/modules/tealBidAdapter.js +++ b/modules/tealBidAdapter.js @@ -1,7 +1,7 @@ import {deepSetValue, deepAccess, triggerPixel, deepClone, isEmpty, logError, shuffle} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js' const BIDDER_CODE = 'teal'; const GVLID = 1378; @@ -43,7 +43,7 @@ const converter = ortbConverter({ export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], aliases: [], isBidRequestValid: function(bid) { diff --git a/modules/teqBlazeSalesAgentBidAdapter.js b/modules/teqBlazeSalesAgentBidAdapter.js new file mode 100644 index 00000000000..f2cbf2d57db --- /dev/null +++ b/modules/teqBlazeSalesAgentBidAdapter.js @@ -0,0 +1,41 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { + buildPlacementProcessingFunction, + buildRequestsBase, + interpretResponse, + isBidRequestValid +} from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'teqBlazeSalesAgent'; +const AD_URL = 'https://be-agent.teqblaze.io/pbjs'; + +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + const aeeSignals = bidderRequest.ortb2?.site?.ext?.data?.scope3_aee; + + if (aeeSignals) { + placement.axei = aeeSignals.include; + placement.axex = aeeSignals.exclude; + + if (aeeSignals.macro) { + placement.axem = aeeSignals.macro; + } + } +}; + +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: isBidRequestValid(['placementId']), + buildRequests, + interpretResponse +}; + +registerBidder(spec); diff --git a/modules/teqBlazeSalesAgentBidAdapter.md b/modules/teqBlazeSalesAgentBidAdapter.md new file mode 100644 index 00000000000..d0c1475643d --- /dev/null +++ b/modules/teqBlazeSalesAgentBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: TeqBlaze Sales Agent Bidder Adapter +Module Type: TeqBlaze Sales Agent Bidder Adapter +Maintainer: support@teqblaze.com +``` + +# Description + +Connects to TeqBlaze Sales Agent for bids. +TeqBlaze Sales Agent 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: 'teqBlazeSalesAgent', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'teqBlazeSalesAgent', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'teqBlazeSalesAgent', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index 594600b30e8..6bc86ba8c98 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -16,6 +16,8 @@ const events = { bids: [] }; +let adUnitMap = new Map(); + var terceptAnalyticsAdapter = Object.assign(adapter( { emptyUrl, @@ -29,21 +31,34 @@ var terceptAnalyticsAdapter = Object.assign(adapter( Object.assign(events, {bids: []}); events.auctionInit = args; auctionTimestamp = args.timestamp; + adUnitMap.set(args.auctionId, args.adUnits); } else if (eventType === EVENTS.BID_REQUESTED) { mapBidRequests(args).forEach(item => { events.bids.push(item) }); } else if (eventType === EVENTS.BID_RESPONSE) { mapBidResponse(args, 'response'); } else if (eventType === EVENTS.NO_BID) { mapBidResponse(args, 'no_bid'); + } else if (eventType === EVENTS.BIDDER_ERROR) { + send({ + bidderError: mapBidResponse(args, 'bidder_error') + }); } else if (eventType === EVENTS.BID_WON) { send({ bidWon: mapBidResponse(args, 'win') - }, 'won'); + }); + } else if (eventType === EVENTS.AD_RENDER_SUCCEEDED) { + send({ + adRenderSucceeded: mapBidResponse(args, 'render_succeeded') + }); + } else if (eventType === EVENTS.AD_RENDER_FAILED) { + send({ + adRenderFailed: mapBidResponse(args, 'render_failed') + }); } } if (eventType === EVENTS.AUCTION_END) { - send(events, 'auctionEnd'); + send(events); } } }); @@ -68,28 +83,93 @@ function mapBidRequests(params) { return arr; } +function getAdSlotData(auctionId, adUnitCode) { + const auctionAdUnits = adUnitMap?.get(auctionId); + + if (!Array.isArray(auctionAdUnits)) { + return {}; + } + + const matchingAdUnit = auctionAdUnits.find(au => au.code === adUnitCode); + + return { + adserverAdSlot: matchingAdUnit?.ortb2Imp?.ext?.data?.adserver?.adslot, + pbAdSlot: matchingAdUnit?.ortb2Imp?.ext?.data?.pbadslot, + }; +} + function mapBidResponse(bidResponse, status) { - if (status !== 'win') { - const bid = events.bids.filter(o => o.bidId === bidResponse.bidId || o.bidId === bidResponse.requestId)[0]; + const isRenderEvent = (status === 'render_succeeded' || status === 'render_failed'); + const bid = isRenderEvent ? bidResponse.bid : bidResponse; + const { adserverAdSlot, pbAdSlot } = getAdSlotData(bid?.auctionId, bid?.adUnitCode); + + if (status === 'bidder_error') { + return { + ...bidResponse, + adserverAdSlot: adserverAdSlot, + pbAdSlot: pbAdSlot, + status: 6, + host: window.location.hostname, + path: window.location.pathname, + search: window.location.search + } + } else if (status !== 'win') { + const existingBid = isRenderEvent ? null : events.bids.filter(o => o.bidId === bid.bidId || o.bidId === bid.requestId)[0]; const responseTimestamp = Date.now(); - Object.assign(bid, { - bidderCode: bidResponse.bidder, - bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, - adUnitCode: bidResponse.adUnitCode, - auctionId: bidResponse.auctionId, - creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, - currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, - mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - status: bidResponse.status, - renderStatus: status === 'timeout' ? 3 : (status === 'no_bid' ? 5 : 2), - timeToRespond: bidResponse.timeToRespond, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp ? bidResponse.responseTimestamp : responseTimestamp - }); + + const getRenderStatus = () => { + if (status === 'timeout') return 3; + if (status === 'no_bid') return 5; + if (status === 'render_succeeded') return 7; + if (status === 'render_failed') return 8; + return 2; + }; + + const mappedData = { + bidderCode: bid.bidder, + bidId: (status === 'timeout' || status === 'no_bid') ? bid.bidId : bid.requestId, + adUnitCode: bid.adUnitCode, + auctionId: bid.auctionId, + creativeId: bid.creativeId, + transactionId: bid.transactionId, + currency: bid.currency, + cpm: bid.cpm, + netRevenue: bid.netRevenue, + renderedSize: isRenderEvent ? bid.size : null, + width: bid.width, + height: bid.height, + mediaType: bid.mediaType, + statusMessage: bid.statusMessage, + status: bid.status, + renderStatus: getRenderStatus(), + timeToRespond: bid.timeToRespond, + requestTimestamp: bid.requestTimestamp, + responseTimestamp: bid.responseTimestamp ? bid.responseTimestamp : responseTimestamp, + renderTimestamp: isRenderEvent ? Date.now() : null, + reason: status === 'render_failed' ? bidResponse.reason : null, + message: status === 'render_failed' ? bidResponse.message : null, + host: isRenderEvent ? window.location.hostname : null, + path: isRenderEvent ? window.location.pathname : null, + search: isRenderEvent ? window.location.search : null, + adserverAdSlot: adserverAdSlot, + pbAdSlot: pbAdSlot, + ttl: bid.ttl, + dealId: bid.dealId, + ad: isRenderEvent ? null : bid.ad, + adUrl: isRenderEvent ? null : bid.adUrl, + adId: bid.adId, + size: isRenderEvent ? null : bid.size, + adserverTargeting: isRenderEvent ? null : bid.adserverTargeting, + videoCacheKey: isRenderEvent ? null : bid.videoCacheKey, + native: isRenderEvent ? null : bid.native, + meta: bid.meta || {} + }; + + if (isRenderEvent) { + return mappedData; + } else { + Object.assign(existingBid, mappedData); + } } else { return { bidderCode: bidResponse.bidder, @@ -102,6 +182,8 @@ function mapBidResponse(bidResponse, status) { cpm: bidResponse.cpm, netRevenue: bidResponse.netRevenue, renderedSize: bidResponse.size, + width: bidResponse.width, + height: bidResponse.height, mediaType: bidResponse.mediaType, statusMessage: bidResponse.statusMessage, status: bidResponse.status, @@ -109,14 +191,28 @@ function mapBidResponse(bidResponse, status) { timeToRespond: bidResponse.timeToRespond, requestTimestamp: bidResponse.requestTimestamp, responseTimestamp: bidResponse.responseTimestamp, + renderTimestamp: null, + reason: null, + message: null, host: window.location.hostname, path: window.location.pathname, - search: window.location.search + search: window.location.search, + adserverAdSlot: adserverAdSlot, + pbAdSlot: pbAdSlot, + ttl: bidResponse.ttl, + dealId: bidResponse.dealId, + ad: bidResponse.ad, + adUrl: bidResponse.adUrl, + adId: bidResponse.adId, + adserverTargeting: bidResponse.adserverTargeting, + videoCacheKey: bidResponse.videoCacheKey, + native: bidResponse.native, + meta: bidResponse.meta || {} } } } -function send(data, status) { +function send(data) { const location = getWindowLocation(); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { Object.assign(data.auctionInit, { host: location.host, path: location.pathname, search: location.search }); diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index 15ac4376548..d57e307c7e1 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -9,6 +9,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -374,11 +375,11 @@ const buildDeviceComponent = (bidRequest, bidderRequest) => { dnt: getDNT() ? 1 : 0, }; // Include connection info if available - const CONNECTION = navigator.connection || navigator.webkitConnection; - if (CONNECTION && CONNECTION.type) { - device['connectiontype'] = CONNECTION.type; - if (CONNECTION.downlinkMax) { - device['connectionDownlinkMax'] = CONNECTION.downlinkMax; + const connection = getConnectionInfo(); + if (connection?.type) { + device['connectiontype'] = connection.type; + if (connection.downlinkMax != null) { + device['connectionDownlinkMax'] = connection.downlinkMax; } } diff --git a/modules/timeoutRtdProvider.js b/modules/timeoutRtdProvider.js index fd7f639a1db..72ed6b7b9f8 100644 --- a/modules/timeoutRtdProvider.js +++ b/modules/timeoutRtdProvider.js @@ -2,6 +2,7 @@ import { submodule } from '../src/hook.js'; import * as ajax from '../src/ajax.js'; import { logInfo, deepAccess, logError } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { bidderTimeoutFunctions } from '../libraries/bidderTimeoutUtils/bidderTimeoutUtils.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -11,113 +12,9 @@ const SUBMODULE_NAME = 'timeout'; // this allows the stubbing of functions during testing export const timeoutRtdFunctions = { - getDeviceType, - getConnectionSpeed, - checkVideo, - calculateTimeoutModifier, handleTimeoutIncrement }; -const entries = Object.entries || function(obj) { - const ownProps = Object.keys(obj); - let i = ownProps.length; - const resArray = new Array(i); - while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; } - return resArray; -}; - -function getDeviceType() { - const userAgent = window.navigator.userAgent.toLowerCase(); - if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(userAgent))) { - return 5; // tablet - } - if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(userAgent))) { - return 4; // mobile - } - return 2; // personal computer -} - -function checkVideo(adUnits) { - return adUnits.some((adUnit) => { - return adUnit.mediaTypes && adUnit.mediaTypes.video; - }); -} - -function getConnectionSpeed() { - const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection || {} - const connectionType = connection.type || connection.effectiveType; - - switch (connectionType) { - case 'slow-2g': - case '2g': - return 'slow'; - - case '3g': - return 'medium'; - - case 'bluetooth': - case 'cellular': - case 'ethernet': - case 'wifi': - case 'wimax': - case '4g': - return 'fast'; - } - - return 'unknown'; -} -/** - * Calculate the time to be added to the timeout - * @param {Array} adUnits - * @param {Object} rules - * @return {number} - */ -function calculateTimeoutModifier(adUnits, rules) { - logInfo('Timeout rules', rules); - let timeoutModifier = 0; - let toAdd = 0; - - if (rules.includesVideo) { - const hasVideo = timeoutRtdFunctions.checkVideo(adUnits); - toAdd = rules.includesVideo[hasVideo] || 0; - logInfo(`Adding ${toAdd} to timeout for includesVideo ${hasVideo}`) - timeoutModifier += toAdd; - } - - if (rules.numAdUnits) { - const numAdUnits = adUnits.length; - if (rules.numAdUnits[numAdUnits]) { - timeoutModifier += rules.numAdUnits[numAdUnits]; - } else { - for (const [rangeStr, timeoutVal] of entries(rules.numAdUnits)) { - const [lowerBound, upperBound] = rangeStr.split('-'); - if (parseInt(lowerBound) <= numAdUnits && numAdUnits <= parseInt(upperBound)) { - logInfo(`Adding ${timeoutVal} to timeout for numAdUnits ${numAdUnits}`) - timeoutModifier += timeoutVal; - break; - } - } - } - } - - if (rules.deviceType) { - const deviceType = timeoutRtdFunctions.getDeviceType(); - toAdd = rules.deviceType[deviceType] || 0; - logInfo(`Adding ${toAdd} to timeout for deviceType ${deviceType}`) - timeoutModifier += toAdd; - } - - if (rules.connectionSpeed) { - const connectionSpeed = timeoutRtdFunctions.getConnectionSpeed(); - toAdd = rules.connectionSpeed[connectionSpeed] || 0; - logInfo(`Adding ${toAdd} to timeout for connectionSpeed ${connectionSpeed}`) - timeoutModifier += toAdd; - } - - logInfo('timeout Modifier calculated', timeoutModifier); - return timeoutModifier; -} - /** * * @param {Object} reqBidsConfigObj @@ -161,7 +58,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { */ function handleTimeoutIncrement(reqBidsConfigObj, rules) { const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; - const timeoutModifier = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); + const timeoutModifier = bidderTimeoutFunctions.calculateTimeoutModifier(adUnits, rules); const bidderTimeout = reqBidsConfigObj.timeout || getGlobal().getConfig('bidderTimeout'); reqBidsConfigObj.timeout = bidderTimeout + timeoutModifier; } diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js index 960d7858117..3fb613ff728 100644 --- a/modules/topLevelPaapi.js +++ b/modules/topLevelPaapi.js @@ -17,12 +17,10 @@ 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(); } @@ -40,31 +38,27 @@ function bidIfRenderable(bid) { return bid; } -const forRenderStack = []; - -function renderPaapiHook(next, adId, forRender = true, override = PbPromise.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; +function renderPaapiHook(next, adId, forRender = true, cb) { + PbPromise + .resolve() + .then(() => { + const ids = parsePaapiAdId(adId); + if (ids) { + 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); + }); + } + }) + .then((bid) => { + if (bid != null) return bid; + return new Promise(resolve => next(adId, forRender, resolve)) + }) + .then((bid) => { + if (bid == null || isPaapiBid(bid) || bid?.status === BID_STATUS.RENDERED) return bid; return getPAAPIBids({adUnitCode: bid.adUnitCode}).then(res => { const paapiBid = bidIfRenderable(res[bid.adUnitCode]); if (paapiBid) { @@ -77,9 +71,8 @@ function renderOverrideHook(next, bidPm) { } return bid; }); - }); - } - next(bidPm); + }) + .then(cb); } export function getRenderingDataHook(next, bid, options) { diff --git a/modules/toponBidAdapter.js b/modules/toponBidAdapter.js index 263069b7f34..5df8f3ff943 100644 --- a/modules/toponBidAdapter.js +++ b/modules/toponBidAdapter.js @@ -9,6 +9,10 @@ const LOG_PREFIX = "TopOn"; const GVLID = 1305; const ENDPOINT = "https://web-rtb.anyrtb.com/ortb/prebid"; const DEFAULT_TTL = 360; +const USER_SYNC_URL = "https://pb.anyrtb.com/pb/page/prebidUserSyncs.html"; +const USER_SYNC_IMG_URL = "https://cm.anyrtb.com/cm/sdk_sync"; + +let lastPubid; const converter = ortbConverter({ context: { @@ -98,6 +102,7 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { const { pubid } = bidderRequest?.bids?.[0]?.params || {}; + lastPubid = pubid; const ortbRequest = converter.toORTB({ validBidRequests, bidderRequest }); const url = ENDPOINT + "?pubid=" + pubid; @@ -156,13 +161,64 @@ export const spec = { return bids; }, - getUserSyncs: ( + getUserSyncs: function ( syncOptions, responses, gdprConsent, uspConsent, gppConsent - ) => {}, + ) { + const pubid = lastPubid; + const syncs = []; + const params = []; + + if (typeof pubid === "string" && pubid.length > 0) { + params.push(`pubid=tpn${encodeURIComponent(pubid)}`); + } + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === "boolean") { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); + } + if (typeof gdprConsent.consentString === "string") { + params.push(`consent=${encodeURIComponent(gdprConsent.consentString)}`); + } + } + if (uspConsent) { + params.push(`us_privacy=${encodeURIComponent(uspConsent)}`); + } + if (gppConsent) { + if (typeof gppConsent.gppString === "string") { + params.push(`gpp=${encodeURIComponent(gppConsent.gppString)}`); + } + if (Array.isArray(gppConsent.applicableSections)) { + params.push( + `gpp_sid=${encodeURIComponent( + gppConsent.applicableSections.join(",") + )}` + ); + } + } + if (syncOptions?.iframeEnabled) { + const syncUrl = `${USER_SYNC_URL}${ + params.length > 0 ? "?" + params.join("&") : "" + }`; + + syncs.push({ + type: "iframe", + url: syncUrl, + }); + } else if (syncOptions?.pixelEnabled) { + const syncUrl = `${USER_SYNC_IMG_URL}${ + params.length > 0 ? "?" + params.join("&") : "" + }`; + + syncs.push({ + type: "image", + url: syncUrl, + }); + } + return syncs; + }, onBidWon: (bid) => { logWarn(`[${LOG_PREFIX}] Bid won: ${JSON.stringify(bid)}`); }, diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js new file mode 100644 index 00000000000..2b0f2c80331 --- /dev/null +++ b/modules/trustxBidAdapter.js @@ -0,0 +1,456 @@ +import { + logInfo, + logError, + logMessage, + deepAccess, + deepSetValue, + mergeDeep +} 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 {ortbConverter} from '../libraries/ortbConverter/converter.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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + */ + +const BIDDER_CODE = 'trustx'; +const BID_TTL = 360; +const NET_REVENUE = false; +const SUPPORTED_CURRENCY = 'USD'; +const OUTSTREAM_PLAYER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const ADAPTER_VERSION = '1.0'; + +const ortbAdapterConverter = ortbConverter({ + context: { + netRevenue: NET_REVENUE, + ttl: BID_TTL + }, + imp(buildImp, bidRequest, context) { + const impression = buildImp(bidRequest, context); + const params = bidRequest.params || {}; + + if (!impression.bidfloor) { + let floor = parseFloat(params.bidfloor || params.bidFloor || params.floorcpm || 0) || 0; + + if (typeof bidRequest.getFloor === 'function') { + const mediaTypes = bidRequest.mediaTypes || {}; + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + const floorInfo = bidRequest.getFloor({ + currency: SUPPORTED_CURRENCY, + mediaType: curMediaType, + size: bidRequest.sizes ? bidRequest.sizes.map(([w, h]) => ({w, h})) : '*' + }); + + if (floorInfo && typeof floorInfo === 'object' && + floorInfo.currency === SUPPORTED_CURRENCY && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + impression.bidfloor = floor; + impression.bidfloorcur = params.currency || SUPPORTED_CURRENCY; + } + + const tagId = params.uid || params.secid; + if (tagId) { + impression.tagid = tagId.toString(); + } + + if (bidRequest.adUnitCode) { + if (!impression.ext) { + impression.ext = {}; + } + impression.ext.divid = bidRequest.adUnitCode.toString(); + } + + if (impression.banner && impression.banner.format && impression.banner.format.length > 0) { + const firstFormat = impression.banner.format[0]; + if (firstFormat.w && firstFormat.h && (impression.banner.w == null || impression.banner.h == null)) { + impression.banner.w = firstFormat.w; + impression.banner.h = firstFormat.h; + } + } + + return impression; + }, + request(buildRequest, imps, bidderRequest, context) { + const requestObj = buildRequest(imps, bidderRequest, context); + mergeDeep(requestObj, { + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: ADAPTER_VERSION, + } + }); + + if (!requestObj.source) { + requestObj.source = {}; + } + + if (!requestObj.source.tid && bidderRequest.ortb2?.source?.tid) { + requestObj.source.tid = bidderRequest.ortb2.source.tid.toString(); + } + + if (!requestObj.source.ext) { + requestObj.source.ext = {}; + } + requestObj.source.ext.wrapper = 'Prebid_js'; + requestObj.source.ext.wrapper_version = '$prebid.version$'; + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(requestObj, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // GPP + if (bidderRequest.gppConsent?.gppString) { + deepSetValue(requestObj, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(requestObj, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest.ortb2?.regs?.gpp) { + deepSetValue(requestObj, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(requestObj, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + + // COPPA + if (config.getConfig('coppa') === true) { + deepSetValue(requestObj, 'regs.coppa', 1); + } + + // User IDs (eIDs) + if (bidderRequest.bidRequests && bidderRequest.bidRequests.length > 0) { + const bidRequest = bidderRequest.bidRequests[0]; + if (bidRequest.userIdAsEids && bidRequest.userIdAsEids.length > 0) { + deepSetValue(requestObj, 'user.ext.eids', bidRequest.userIdAsEids); + } + + // Supply Chain (schain) + if (bidRequest.schain) { + deepSetValue(requestObj, 'source.ext.schain', bidRequest.schain); + } + } + + if (requestObj.tmax == null && bidderRequest.timeout) { + const timeout = parseInt(bidderRequest.timeout, 10); + if (!isNaN(timeout)) { + requestObj.tmax = timeout; + } + } + + return requestObj; + }, + bidResponse(buildBidResponse, bid, context) { + const {bidRequest} = context; + let responseMediaType; + + if (bid.mtype === 2) { + responseMediaType = VIDEO; + } else if (bid.mtype === 1) { + responseMediaType = BANNER; + } else { + responseMediaType = BANNER; + } + + context.mediaType = responseMediaType; + context.currency = SUPPORTED_CURRENCY; + + if (responseMediaType === VIDEO) { + context.vastXml = bid.adm; + } + + const bidResponseObj = buildBidResponse(bid, context); + + if (bid.ext?.bidder?.trustx?.networkName) { + bidResponseObj.adserverTargeting = { 'hb_ds': bid.ext.bidder.trustx.networkName }; + if (!bidResponseObj.meta) { + bidResponseObj.meta = {}; + } + bidResponseObj.meta.demandSource = bid.ext.bidder.trustx.networkName; + } + + if (responseMediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { + bidResponseObj.renderer = setupOutstreamRenderer(bidResponseObj); + bidResponseObj.adResponse = { + content: bidResponseObj.vastXml, + width: bidResponseObj.width, + height: bidResponseObj.height + }; + } + + return bidResponseObj; + } +}); + +export const spec = { + code: BIDDER_CODE, + VERSION: ADAPTER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + ENDPOINT: 'https://ads.trustx.org/pbhb', + + /** + * 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 ( + isParamsValid(bid) && + isBannerValid(bid) && + isVideoValid(bid) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests + * @param {object} bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const adType = containsVideoRequest(validBidRequests) ? VIDEO : BANNER; + const requestData = ortbAdapterConverter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context: {contextMediaType: adType} + }); + + if (validBidRequests[0].params.test) { + logMessage('trustx test mode enabled'); + } + + return { + method: 'POST', + url: spec.ENDPOINT, + data: requestData + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {object} bidRequest The bid request object. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + return ortbAdapterConverter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data + }).bids; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {object[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses) { + logInfo('trustx.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncElements = []; + + // Early return if sync is completely disabled + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncElements; + } + + // Server always returns ext.usersync, so we extract sync URLs from server response + if (serverResponses && Array.isArray(serverResponses) && serverResponses.length > 0) { + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + value.syncs.forEach(syncItem => { + syncElements.push({ + type: syncItem.type === 'iframe' ? 'iframe' : 'image', + url: syncItem.url + }); + }); + } + }); + } + }); + + // Per requirement: If iframeEnabled, return only iframes; + // if not iframeEnabled but pixelEnabled, return only pixels + if (syncOptions.iframeEnabled) { + syncElements = syncElements.filter(s => s.type === 'iframe'); + } else if (syncOptions.pixelEnabled) { + syncElements = syncElements.filter(s => s.type === 'image'); + } + } + + logInfo('trustx.getUserSyncs result=%o', syncElements); + return syncElements; + } +}; + +/** + * Creates an outstream renderer for video ads + * @param {Bid} bid The bid response + * @return {Renderer} A renderer for outstream video + */ +function setupOutstreamRenderer(bid) { + const renderer = Renderer.install({ + id: bid.adId, + url: OUTSTREAM_PLAYER_URL, + loaded: false + }); + + renderer.setRender(outstreamRender); + + return renderer; +} + +/** + * Outstream renderer function called by the renderer + * @param {Object} bid The bid object + */ +function outstreamRender(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +/* ======== + * Helpers + *========= + */ + +/** + * Checks if the bid request has banner media type + * @param {BidRequest} bidRequest + * @return {boolean} True if has banner media type + */ +function hasBannerFormat(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * Checks if the bid request has video media type + * @param {BidRequest|BidRequest[]} bidRequest bid request or array of bid requests + * @return {boolean} True if has video media type + */ +function containsVideoRequest(bidRequest) { + if (Array.isArray(bidRequest)) { + return bidRequest.some(bid => !!deepAccess(bid, 'mediaTypes.video')); + } + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +/** + * Validates basic bid parameters + * @param {BidRequest} bidRequest + * @return {boolean} True if parameters are valid + */ +function isParamsValid(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (bidRequest.params.test) { + return true; + } + + const hasTagId = bidRequest.params.uid || bidRequest.params.secid; + + if (!hasTagId) { + logError('trustx validation failed: Placement ID (uid or secid) not declared'); + return false; + } + + const hasMediaType = containsVideoRequest(bidRequest) || hasBannerFormat(bidRequest); + if (!hasMediaType) { + return false; + } + + return true; +} + +/** + * Validates banner bid request + * @param {BidRequest} bidRequest + * @return {boolean} True if valid banner bid request + */ +function isBannerValid(bidRequest) { + // If there's no banner no need to validate + if (!hasBannerFormat(bidRequest)) { + return true; + } + + const banner = deepAccess(bidRequest, 'mediaTypes.banner'); + if (!Array.isArray(banner.sizes)) { + return false; + } + + return true; +} + +/** + * Validates video bid request + * @param {BidRequest} bidRequest + * @return {boolean} True if valid video bid request + */ +function isVideoValid(bidRequest) { + // If there's no video no need to validate + if (!containsVideoRequest(bidRequest)) { + return true; + } + + const videoConfig = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidParams = deepAccess(bidRequest, 'params.video', {}); + const params = deepAccess(bidRequest, 'params', {}); + + if (params && params.test) { + return true; + } + + const consolidatedVideoParams = { + ...videoConfig, + ...videoBidParams // Bidder Specific overrides + }; + + if (!Array.isArray(consolidatedVideoParams.mimes) || consolidatedVideoParams.mimes.length === 0) { + logError('trustx validation failed: mimes are invalid'); + return false; + } + + if (!Array.isArray(consolidatedVideoParams.protocols) || consolidatedVideoParams.protocols.length === 0) { + logError('trustx validation failed: protocols are invalid'); + return false; + } + + if (!consolidatedVideoParams.context) { + logError('trustx validation failed: context id not declared'); + return false; + } + + if (consolidatedVideoParams.context !== 'instream' && consolidatedVideoParams.context !== 'outstream') { + logError('trustx validation failed: only context instream or outstream is supported'); + return false; + } + + if (typeof consolidatedVideoParams.playerSize === 'undefined' || !Array.isArray(consolidatedVideoParams.playerSize) || !Array.isArray(consolidatedVideoParams.playerSize[0])) { + logError('trustx validation failed: player size not declared or is not in format [[w,h]]'); + return false; + } + + return true; +} + +registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md new file mode 100644 index 00000000000..c9634201cb1 --- /dev/null +++ b/modules/trustxBidAdapter.md @@ -0,0 +1,298 @@ +# Overview + +``` +Module Name: TRUSTX Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@trustx.org +``` + +# Description + +Module that connects to TRUSTX's premium demand sources. +TRUSTX bid adapter supports Banner and Video ad formats with advanced targeting capabilities. + +# Required Parameters + +## Banner + +- `uid` or `secid` (required) - Placement ID / Tag ID +- `mediaTypes.banner.sizes` (required) - Array of banner sizes + +## Video + +- `uid` or `secid` (required) - Placement ID / Tag ID +- `mediaTypes.video.context` (required) - Must be 'instream' or 'outstream' +- `mediaTypes.video.playerSize` (required) - Array format [[w, h]] +- `mediaTypes.video.mimes` (required) - Array of MIME types +- `mediaTypes.video.protocols` (required) - Array of protocol numbers + +# Test Parameters + +## Banner + +``` +var adUnits = [ + { + code: 'trustx-banner-container', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [728, 90]] + } + }, + bids: [{ + bidder: 'trustx', + params: { + uid: 123456, + bidFloor: 0.5 + } + }] + } +]; +``` + +## Video + +We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video`: +- 'mimes' +- 'minduration' +- 'maxduration' +- 'plcmt' +- 'protocols' +- 'startdelay' +- 'skip' +- 'skipafter' +- 'minbitrate' +- 'maxbitrate' +- 'delivery' +- 'playbackmethod' +- 'api' +- 'linearity' + +## Instream Video adUnit using mediaTypes.video + +*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. +*Note:* The TRUSTX ad server will respond with a VAST XML to load into your defined player. + +``` +var adUnits = [ + { + code: 'trustx-video-container', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + mimes: ['video/mp4', 'video/webm'], + protocols: [2, 3, 5, 6], + api: [2, 7], + position: 1, + delivery: [2], + minduration: 5, + maxduration: 60, + plcmt: 1, + playbackmethod: [1, 3, 5], + } + }, + bids: [ + { + bidder: 'trustx', + params: { + uid: 123456, + bidFloor: 5.0 + } + } + ] + } +] +``` + +## Outstream Video + +TRUSTX also supports outstream video format that can be displayed in non-video placements. + +``` +var adUnits = [ + { + code: 'trustx-outstream-container', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 360]], + mimes: ['video/mp4', 'video/webm'], + protocols: [2, 3, 5, 6], + api: [2, 7], + placement: 3, + minduration: 5, + maxduration: 30, + } + }, + bids: [ + { + bidder: 'trustx', + params: { + uid: 123456, + bidFloor: 6.0 + } + } + ] + } +] +``` + +# Test Mode + +By passing `bid.params.test = true` you will be able to receive a test creative without needing to set up real placements. + +## Banner + +``` +var adUnits = [ + { + code: 'trustx-test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [728, 90]] + } + }, + bids: [{ + bidder: 'trustx', + params: { + test: true + } + }] + } +]; +``` + +## Video + +``` +var adUnits = [ + { + code: 'trustx-test-video', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4', 'video/webm'], + protocols: [2, 3, 5, 6], + } + }, + bids: [ + { + bidder: 'trustx', + params: { + test: true + } + } + ] + } +] +``` + +# Optional Parameters + +## Bid Floor + +You can specify bid floor using any of these parameter names: +- `bidFloor` (camelCase) +- `bidfloor` (lowercase) +- `floorcpm` (alternative name) + +The adapter also supports Prebid's Floor Module via `getFloor()` function. The highest value between params and Floor Module will be used. + +``` +params: { + uid: 455069, + bidFloor: 0.5, // or bidfloor or floorcpm + currency: 'USD' // optional, defaults to USD +} +``` + +## Backward Compatibility with Grid Adapter + +The TRUSTX adapter is fully compatible with Grid adapter parameters for seamless migration: + +- `uid` - Same as Grid adapter (required) +- `secid` - Alternative to uid (required if uid not provided) +- `bidFloor` / `bidfloor` / `floorcpm` - Bid floor (optional) + +# First Party Data (FPD) Support + +The adapter automatically includes First Party Data from `ortb2` configuration: + +## Site FPD + +``` +pbjs.setConfig({ + ortb2: { + site: { + name: 'Example Site', + domain: 'example.com', + page: 'https://example.com/page', + cat: ['IAB12-1'], + content: { + data: [{ + name: 'reuters.com', + segment: [{id: '391'}, {id: '52'}] + }] + } + } + } +}); +``` + +## User FPD + +User IDs are passed through Prebid's User ID modules (e.g., SharedId) via `user.ext.eids`. + +## Device FPD + +Device data from `ortb2.device` is automatically included in requests. + +# User Sync + +The adapter supports server-side user sync. Sync URLs are extracted from server response (`ext.usersync`) and automatically registered with Prebid.js. + +``` +pbjs.setConfig({ + userSync: { + syncEnabled: true, + iframeEnabled: true, + pixelEnabled: true + } +}); +``` + +# Additional Configuration Options + +## GPP Support + +TRUSTX fully supports Global Privacy Platform (GPP) standards. GPP data is automatically passed from `bidderRequest.gppConsent` or `ortb2.regs.gpp`. + +## CCPA Support + +CCPA (US Privacy) data is automatically passed from `bidderRequest.uspConsent` or `ortb2.regs.ext.us_privacy`. + +## COPPA Support + +COPPA compliance is automatically handled when `config.getConfig('coppa') === true`. + +## DSA Support + +Digital Services Act (DSA) data is automatically passed from `ortb2.regs.ext.dsa`. + +## Supply Chain (schain) + +Supply chain data is automatically passed from `ortb2.source.ext.schain` or `bidRequest.schain`. + +## Source Transaction ID (tid) + +Transaction ID is automatically passed from `ortb2.source.tid`. + +# Response Format + +The adapter returns bids in standard Prebid.js format with the following additional fields: + +- `adserverTargeting.hb_ds` - Network name from server response (`ext.bidder.trustx.networkName`) +- `meta.demandSource` - Network name metadata (same as `networkName` from server) +- `netRevenue: false` - Revenue model \ No newline at end of file diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 4598c3b8a7a..d985870b074 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -21,6 +21,7 @@ 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 TTL = 360; const MEDIA_TYPE = { BANNER: 1, @@ -143,6 +144,8 @@ function getImpression(bidRequest) { }; const gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + const exp = TTL; + impression.exp = exp; const tagid = gpid || bidRequest.params.placementId; if (tagid) { impression.tagid = tagid; @@ -477,7 +480,7 @@ export const spec = { dealId: bid.dealid || null, currency: currency || 'USD', netRevenue: true, - ttl: bid.ttl || 360, + ttl: bid.ttl || TTL, meta: {}, }; diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 3f9c4dd1253..bf6b70e26db 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -56,12 +56,6 @@ const RemoveDuplicateSizes = (validBid) => { } }; -const ConfigureProtectedAudience = (validBid, protectedAudienceEnabled) => { - if (!protectedAudienceEnabled && validBid.ortb2Imp && validBid.ortb2Imp.ext) { - delete validBid.ortb2Imp.ext.ae; - } -} - const getRequests = (conf, validBidRequests, bidderRequest) => { const {bids, bidderRequestId, bidderCode, ...bidderRequestData} = bidderRequest; const invalidBidsCount = bidderRequest.bids.length - validBidRequests.length; @@ -71,7 +65,6 @@ const getRequests = (conf, validBidRequests, bidderRequest) => { const currSiteId = validBid.params.siteId; addBidFloorInfo(validBid); RemoveDuplicateSizes(validBid); - ConfigureProtectedAudience(validBid, conf.protectedAudienceEnabled); requestBySiteId[currSiteId] = requestBySiteId[currSiteId] || []; requestBySiteId[currSiteId].push(validBid); }); @@ -226,43 +219,18 @@ export const adapter = { 'options': { 'contentType': 'application/json' }, - 'protectedAudienceEnabled': bidderRequest.paapi?.enabled }, validBidRequests, bidderRequest); }, interpretResponse: function (serverResponse) { - if (!(serverResponse && serverResponse.body && (serverResponse.body.auctionConfigs || serverResponse.body.bids))) { + if (!(serverResponse && serverResponse.body && serverResponse.body.bids)) { return []; } const serverResponseBody = serverResponse.body; - let bids = []; - let fledgeAuctionConfigs = null; - if (serverResponseBody.bids.length) { - bids = handleBidResponseByMediaType(serverResponseBody.bids); - } - - if (serverResponseBody.auctionConfigs) { - const auctionConfigs = serverResponseBody.auctionConfigs; - const bidIdList = Object.keys(auctionConfigs); - if (bidIdList.length) { - bidIdList.forEach((bidId) => { - fledgeAuctionConfigs = [{ - 'bidId': bidId, - 'config': auctionConfigs[bidId] - }]; - }) - } - } + const bids = handleBidResponseByMediaType(serverResponseBody.bids); - if (!fledgeAuctionConfigs) { - return bids; - } - - return { - bids, - paapi: fledgeAuctionConfigs - }; + return bids; } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index f6f62229f53..bdd8a0bb3e8 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -25,6 +25,14 @@ userIdAsEids = [ }] }, + { + source: 'locid.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { source: 'adserver.org', uids: [{ diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 8ffd8f83043..f7bea8fd9f8 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -91,6 +91,16 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } + }, { + name: 'locId', + params: { + endpoint: 'https://id.example.com/locid' + }, + storage: { + type: 'html5', + name: '_locid', + expires: 7 + } }, { name: 'criteo', storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible diff --git a/modules/verbenBidAdapter.js b/modules/verbenBidAdapter.js new file mode 100644 index 00000000000..867f669e93e --- /dev/null +++ b/modules/verbenBidAdapter.js @@ -0,0 +1,17 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'verben'; +const AD_URL = 'https://east-node.verben.com/pbjs'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse +}; + +registerBidder(spec); diff --git a/modules/verbenBidAdapter.md b/modules/verbenBidAdapter.md new file mode 100644 index 00000000000..2d0978bc52f --- /dev/null +++ b/modules/verbenBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Verben Bidder Adapter +Module Type: Verben Bidder Adapter +Maintainer: support_trading@verben.com +``` + +# Description + +Connects to Verben exchange for bids. +Verben 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: 'verben', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'verben', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'verben', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index 9eb796571ca..7a8dec47a28 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {deepClone, parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; const BIDDER_CODE = 'widespace'; const WS_ADAPTER_VERSION = '2.0.1'; @@ -82,10 +83,10 @@ export const spec = { } // Include connection info if available - const CONNECTION = navigator.connection || navigator.webkitConnection; - if (CONNECTION && CONNECTION.type && CONNECTION.downlinkMax) { - data['netinfo.type'] = CONNECTION.type; - data['netinfo.downlinkMax'] = CONNECTION.downlinkMax; + const connection = getConnectionInfo(); + if (connection?.type && connection.downlinkMax != null) { + data['netinfo.type'] = connection.type; + data['netinfo.downlinkMax'] = connection.downlinkMax; } // Include debug data when available diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js index d2e85f40ec7..dd88ea567eb 100644 --- a/modules/wurflRtdProvider.js +++ b/modules/wurflRtdProvider.js @@ -4,259 +4,1272 @@ import { loadExternalScript } from '../src/adloader.js'; import { mergeDeep, prefixLog, + debugTurnedOn, } from '../src/utils.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { getGlobal } from '../src/prebidGlobal.js'; // Constants const REAL_TIME_MODULE = 'realTimeData'; const MODULE_NAME = 'wurfl'; +const MODULE_VERSION = '2.4.0'; // 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_HOST is the host for the WURFL stats endpoint +const STATS_HOST = 'https://stats.prebid.wurflcloud.com' // STATS_ENDPOINT_PATH is the path for the stats endpoint used to send analytics data -const STATS_ENDPOINT_PATH = '/v1/prebid/stats'; +const STATS_ENDPOINT_PATH = '/v2/prebid/stats'; + +// Storage keys for localStorage caching +const WURFL_RTD_STORAGE_KEY = 'wurflrtd'; + +// OpenRTB 2.0 device type constants +// Based on OpenRTB 2.6 specification +const ORTB2_DEVICE_TYPE = { + MOBILE_OR_TABLET: 1, + PERSONAL_COMPUTER: 2, + CONNECTED_TV: 3, + PHONE: 4, + TABLET: 5, + CONNECTED_DEVICE: 6, + SET_TOP_BOX: 7, + OOH_DEVICE: 8 +}; + +// OpenRTB 2.0 device fields that can be enriched from WURFL data +const ORTB2_DEVICE_FIELDS = [ + 'make', 'model', 'devicetype', 'os', 'osv', 'hwv', + 'h', 'w', 'ppi', 'pxratio', 'js' +]; + +// Enrichment type constants +const ENRICHMENT_TYPE = { + UNKNOWN: 'unknown', + NONE: 'none', + LCE: 'lce', + LCE_ERROR: 'lcefailed', + WURFL_PUB: 'wurfl_pub', + WURFL_SSP: 'wurfl_ssp' +}; + +// Consent class constants +const CONSENT_CLASS = { + NO: 0, // No consent/opt-out/COPPA + PARTIAL: 1, // Partial or ambiguous + FULL: 2, // Full consent or non-GDPR region + ERROR: -1 // Error computing consent +}; + +// Default sampling rate constant +const DEFAULT_SAMPLING_RATE = 100; + +// Default over quota constant +const DEFAULT_OVER_QUOTA = 0; + +// A/B test constants +const AB_TEST = { + CONTROL_GROUP: 'control', + TREATMENT_GROUP: 'treatment', + DEFAULT_SPLIT: 0.5, + DEFAULT_NAME: 'unknown', + ENRICHMENT_TYPE_LCE: 'lce', + ENRICHMENT_TYPE_WURFL: 'wurfl' +}; 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(); +// Storage manager for WURFL RTD provider +export const storage = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: MODULE_NAME, +}); -/** - * 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; -} +// bidderEnrichment maps bidder codes to their enrichment type for beacon reporting +let bidderEnrichment; -/** - * 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; +// enrichmentType tracks the overall enrichment type used in the current auction +let enrichmentType; - const bidders = new Set(); - reqBidsConfigObj.adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - bidders.add(bid.bidder); - }); - }); +// wurflId stores the WURFL ID from device data +let wurflId; - let host = WURFL_JS_HOST; - if (altHost) { - host = altHost; - } +// samplingRate tracks the beacon sampling rate (0-100) +let samplingRate; - const url = new URL(host); - url.pathname = WURFL_JS_ENDPOINT_PATH; +// tier stores the WURFL tier from wurfl_pbjs data +let tier; - if (isDebug) { - url.searchParams.set('debug', 'true') +// overQuota stores the over_quota flag from wurfl_pbjs data (possible values: 0, 1) +let overQuota; + +/** + * Safely gets an object from localStorage with JSON parsing + * @param {string} key The storage key + * @returns {Object|null} Parsed object or null if not found/invalid + */ +function getObjectFromStorage(key) { + if (!storage.hasLocalStorage() || !storage.localStorageIsEnabled()) { + return null; } - url.searchParams.set('mode', 'prebid') - url.searchParams.set('wurfl_id', 'true') + try { + const dataStr = storage.getDataFromLocalStorage(key); + return dataStr ? JSON.parse(dataStr) : null; + } catch (e) { + logger.logError(`Error parsing stored data for key ${key}:`, e); + return null; + } +} + +/** + * Safely sets an object to localStorage with JSON stringification + * @param {string} key The storage key + * @param {Object} data The data to store + * @returns {boolean} Success status + */ +function setObjectToStorage(key, data) { + if (!storage.hasLocalStorage() || !storage.localStorageIsEnabled()) { + return false; + } try { - loadExternalScript(url.toString(), MODULE_TYPE_RTD, MODULE_NAME, () => { - logger.logMessage('script injected'); - window.WURFLPromises.complete.then((res) => { - logger.logMessage('received data', res); - if (!res.wurfl_pbjs) { - logger.logError('invalid WURFL.js for Prebid response'); - } else { - enrichBidderRequests(reqBidsConfigObj, bidders, res); - } - callback(); - }); - }); - } catch (err) { - logger.logError(err); - callback(); + storage.setDataInLocalStorage(key, JSON.stringify(data)); + return true; + } catch (e) { + logger.logError(`Error storing data for key ${key}:`, e); + return false; } } /** - * enrichBidderRequests enriches the OpenRTB 2.0 device data with WURFL data for Business Edition + * enrichDeviceFPD enriches the global device object with device data * @param {Object} reqBidsConfigObj Bid request configuration object - * @param {Array} bidders List of bidders - * @param {Object} wjsResponse WURFL.js response + * @param {Object} deviceData Device data to enrich with */ -function enrichBidderRequests(reqBidsConfigObj, bidders, wjsResponse) { - const authBidders = wjsResponse.wurfl_pbjs?.authorized_bidders ?? {}; - const caps = wjsResponse.wurfl_pbjs?.caps ?? []; +function enrichDeviceFPD(reqBidsConfigObj, deviceData) { + if (!deviceData || !reqBidsConfigObj?.ortb2Fragments?.global) { + return; + } - bidders.forEach((bidderCode) => { - if (bidderCode in authBidders) { - // inject WURFL data - enrichedBidders.add(bidderCode); - const data = bidderData(wjsResponse.WURFL, caps, authBidders[bidderCode]); - data['enrich_device'] = true; - enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + const prebidDevice = reqBidsConfigObj.ortb2Fragments.global.device || {}; + const enrichedDevice = {}; + + ORTB2_DEVICE_FIELDS.forEach(field => { + // Check if field already exists in prebid device + if (prebidDevice[field] !== undefined) { return; } - // inject WURFL low entropy data - const data = lowEntropyData(wjsResponse.WURFL, wjsResponse.wurfl_pbjs?.low_entropy_caps); - enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + + // Check if deviceData has a valid value for this field + if (deviceData[field] === undefined) { + return; + } + + // Copy the field value from deviceData to enrichedDevice + enrichedDevice[field] = deviceData[field]; }); + + // Also copy ext field if present (contains ext.wurfl capabilities) + if (deviceData.ext) { + enrichedDevice.ext = deviceData.ext; + } + + // Use mergeDeep to properly merge into global device + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, { device: enrichedDevice }); } /** - * 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 + * enrichDeviceBidder enriches bidder-specific device data with WURFL data + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Set} bidders Set of bidder codes + * @param {WurflJSDevice} wjsDevice WURFL.js device data with permissions and caps */ -export const bidderData = (wurflData, caps, filter) => { - const data = {}; - if ('wurfl_id' in wurflData) { - data['wurfl_id'] = wurflData.wurfl_id; +function enrichDeviceBidder(reqBidsConfigObj, bidders, wjsDevice) { + // Initialize bidder fragments if not present + if (!reqBidsConfigObj.ortb2Fragments.bidder) { + reqBidsConfigObj.ortb2Fragments.bidder = {}; } - caps.forEach((cap, index) => { - if (!filter.includes(index)) { + + const isOverQuota = wjsDevice._isOverQuota(); + + bidders.forEach((bidderCode) => { + const isAuthorized = wjsDevice._isAuthorized(bidderCode); + + if (!isAuthorized) { + // Over quota + unauthorized -> NO ENRICHMENT + if (isOverQuota) { + bidderEnrichment.set(bidderCode, ENRICHMENT_TYPE.NONE); + return; + } + // Under quota + unauthorized -> inherits from global no bidder enrichment + bidderEnrichment.set(bidderCode, ENRICHMENT_TYPE.WURFL_PUB); return; } - if (cap in wurflData) { - data[cap] = wurflData[cap]; + + // From here: bidder IS authorized + const bidderDevice = wjsDevice.Bidder(bidderCode); + bidderEnrichment.set(bidderCode, ENRICHMENT_TYPE.WURFL_SSP); + + // Edge case: authorized but no data (e.g., missing caps) + if (Object.keys(bidderDevice).length === 0) { + return; } + + // Authorized bidder with data to inject + const bd = reqBidsConfigObj.ortb2Fragments.bidder[bidderCode] || {}; + mergeDeep(bd, bidderDevice); + reqBidsConfigObj.ortb2Fragments.bidder[bidderCode] = bd; }); - 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 + * loadWurflJsAsync loads WURFL.js asynchronously and stores response to localStorage + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Set} bidders Set of bidder codes */ -export const lowEntropyData = (wurflData, lowEntropyCaps) => { - const data = {}; - lowEntropyCaps.forEach((cap, _) => { - let value = wurflData[cap]; - if (cap === 'complete_device_name') { - value = value.replace(/Apple (iP(hone|ad|od)).*/, 'Apple iP$2'); - } - data[cap] = value; - }); - if ('model_name' in wurflData) { - data['model_name'] = wurflData.model_name.replace(/(iP(hone|ad|od)).*/, 'iP$2'); +function loadWurflJsAsync(config, bidders) { + const altHost = config.params?.altHost ?? null; + const isDebug = debugTurnedOn(); + + let host = WURFL_JS_HOST; + if (altHost) { + host = altHost; } - if ('brand_name' in wurflData) { - data['brand_name'] = wurflData.brand_name; + + const url = new URL(host); + url.pathname = WURFL_JS_ENDPOINT_PATH; + + // Start timing WURFL.js load + WurflDebugger.wurflJsLoadStart(); + + if (isDebug) { + url.searchParams.set('debug', 'true'); } - if ('wurfl_id' in wurflData) { - data['wurfl_id'] = wurflData.wurfl_id; + + url.searchParams.set('mode', 'prebid2'); + + // Add bidders list for server optimization + if (bidders && bidders.size > 0) { + url.searchParams.set('bidders', Array.from(bidders).join(',')); + } + + // Helper function to load WURFL.js script + const loadWurflJs = (scriptUrl) => { + try { + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, MODULE_NAME, () => { + window.WURFLPromises.complete.then((res) => { + logger.logMessage('async WURFL.js data received', res); + if (res.wurfl_pbjs) { + // Create optimized cache object with only relevant device data + WurflDebugger.cacheWriteStart(); + const cacheData = { + WURFL: res.WURFL, + wurfl_pbjs: res.wurfl_pbjs, + expire_at: Date.now() + (res.wurfl_pbjs.ttl * 1000) + }; + setObjectToStorage(WURFL_RTD_STORAGE_KEY, cacheData); + WurflDebugger.cacheWriteStop(); + logger.logMessage('WURFL.js device cache stored to localStorage'); + } else { + logger.logError('invalid async WURFL.js for Prebid response'); + } + }).catch((err) => { + logger.logError('async WURFL.js promise error:', err); + }); + }); + } catch (err) { + logger.logError('async WURFL.js loading error:', err); + } + }; + + // Collect Client Hints if available, then load script + if (navigator?.userAgentData?.getHighEntropyValues) { + const hints = ['architecture', 'bitness', 'model', 'platformVersion', 'uaFullVersion', 'fullVersionList']; + navigator.userAgentData.getHighEntropyValues(hints) + .then(ch => { + if (ch !== null) { + url.searchParams.set('uach', JSON.stringify(ch)); + } + }) + .finally(() => { + loadWurflJs(url.toString()); + }); + } else { + // Load script immediately when Client Hints not available + loadWurflJs(url.toString()); } - 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 + * shouldSample determines if an action should be taken based on sampling rate + * @param {number} rate Sampling rate from 0-100 (percentage) + * @returns {boolean} True if should proceed, false if should skip */ -export const enrichBidderRequest = (reqBidsConfigObj, bidderCode, wurflData) => { - const ortb2data = { - 'device': { - 'ext': {}, - }, - }; - - const device = reqBidsConfigObj.ortb2Fragments.global.device; - enrichOrtb2DeviceData('make', wurflData.brand_name, device, ortb2data); - enrichOrtb2DeviceData('model', wurflData.model_name, device, ortb2data); - if (wurflData.enrich_device) { - delete wurflData.enrich_device; - enrichOrtb2DeviceData('devicetype', makeOrtb2DeviceType(wurflData), device, ortb2data); - enrichOrtb2DeviceData('os', wurflData.advertised_device_os, device, ortb2data); - enrichOrtb2DeviceData('osv', wurflData.advertised_device_os_version, device, ortb2data); - enrichOrtb2DeviceData('hwv', wurflData.model_name, device, ortb2data); - enrichOrtb2DeviceData('h', wurflData.resolution_height, device, ortb2data); - enrichOrtb2DeviceData('w', wurflData.resolution_width, device, ortb2data); - enrichOrtb2DeviceData('ppi', wurflData.pixel_density, device, ortb2data); - enrichOrtb2DeviceData('pxratio', toNumber(wurflData.density_class), device, ortb2data); - enrichOrtb2DeviceData('js', toNumber(wurflData.ajax_support_javascript), device, ortb2data); - } - ortb2data.device.ext['wurfl'] = wurflData - mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidderCode]: ortb2data }); +function shouldSample(rate) { + if (rate >= 100) { + return true; + } + if (rate <= 0) { + return false; + } + const randomValue = Math.floor(Math.random() * 100); + return randomValue < rate; } /** - * makeOrtb2DeviceType returns the ortb2 device type based on WURFL data - * @param {Object} wurflData WURFL data - * @returns {Number} ortb2 device type - * @see https://www.scientiamobile.com/how-to-populate-iab-openrtb-device-object/ + * getConsentClass calculates the consent classification level + * @param {Object} userConsent User consent data + * @returns {number} Consent class (0, 1, or 2) */ -export function makeOrtb2DeviceType(wurflData) { - if (wurflData.is_mobile) { - if (!('is_phone' in wurflData) || !('is_tablet' in wurflData)) { - return undefined; - } - if (wurflData.is_phone || wurflData.is_tablet) { - return 1; +function getConsentClass(userConsent) { + // Default to no consent if userConsent is not provided or is an empty object + if (!userConsent || Object.keys(userConsent).length === 0) { + return CONSENT_CLASS.NO; + } + + // Check COPPA (Children's Privacy) + if (userConsent.coppa === true) { + return CONSENT_CLASS.NO; + } + + // Check USP/CCPA (US Privacy) + if (userConsent.usp && typeof userConsent.usp === 'string') { + if (userConsent.usp.substring(0, 2) === '1Y') { + return CONSENT_CLASS.NO; } - return 6; } - if (wurflData.is_full_desktop) { - return 2; + + // Check GDPR object exists + if (!userConsent.gdpr) { + return CONSENT_CLASS.FULL; // No GDPR data means not applicable + } + + // Check GDPR applicability - Note: might be in vendorData + const gdprApplies = userConsent.gdpr.gdprApplies === true || userConsent.gdpr.vendorData?.gdprApplies === true; + + if (!gdprApplies) { + return CONSENT_CLASS.FULL; + } + + // GDPR applies - evaluate purposes + const vendorData = userConsent.gdpr.vendorData; + + if (!vendorData || !vendorData.purpose) { + return CONSENT_CLASS.NO; + } + + const purposes = vendorData.purpose; + const consents = purposes.consents || {}; + const legitimateInterests = purposes.legitimateInterests || {}; + + // Count allowed purposes (7, 8, 10) + let allowedCount = 0; + + // Purpose 7: Measure ad performance + if (consents['7'] === true || legitimateInterests['7'] === true) { + allowedCount++; } - if (wurflData.is_connected_tv) { - return 3; + + // Purpose 8: Market research + if (consents['8'] === true || legitimateInterests['8'] === true) { + allowedCount++; } - if (wurflData.is_phone) { - return 4; + + // Purpose 10: Develop/improve products + if (consents['10'] === true || legitimateInterests['10'] === true) { + allowedCount++; } - if (wurflData.is_tablet) { - return 5; + + // Classify based on allowed purposes count + if (allowedCount === 0) { + return CONSENT_CLASS.NO; } - if (wurflData.is_ott) { - return 7; + if (allowedCount === 3) { + return CONSENT_CLASS.FULL; } - return undefined; + return CONSENT_CLASS.PARTIAL; +} + +// ==================== CLASSES ==================== + +// WurflDebugger object for performance tracking and debugging +const WurflDebugger = { + // Private timing start values + _moduleExecutionStart: null, + _cacheReadStart: null, + _lceDetectionStart: null, + _cacheWriteStart: null, + _wurflJsLoadStart: null, + + // Initialize WURFL debug tracking + init() { + if (!debugTurnedOn()) { + // Replace all methods (except init) with no-ops for zero overhead + Object.keys(this).forEach(key => { + if (typeof this[key] === 'function' && key !== 'init') { + this[key] = () => { }; + } + }); + return; + } + + // Full debug mode - create/reset window object for tracking + if (typeof window !== 'undefined') { + window.WurflRtdDebug = { + // Module version + version: MODULE_VERSION, + + // Prebid.js version + pbjsVersion: getGlobal().version, + + // Data source for current auction + dataSource: 'unknown', // 'cache' | 'lce' + + // Cache state + cacheExpired: false, // Whether the cache was expired when used + + // Simple timing measurements + moduleExecutionTime: null, // Total time from getBidRequestData start to callback + cacheReadTime: null, // Single cache read time (hit or miss) + lceDetectionTime: null, // LCE detection time (only if dataSource = 'lce') + cacheWriteTime: null, // Async cache write time (for future auctions) + wurflJsLoadTime: null, // Total time from WURFL.js load start to cache complete + + // The actual data used in current auction + data: { + // When dataSource = 'cache' + wurflData: null, // The cached WURFL device data + pbjsData: null, // The cached wurfl_pbjs data + + // When dataSource = 'lce' + lceDevice: null // The LCE-generated device object + }, + + // Beacon payload sent to analytics endpoint + beaconPayload: null + }; + } + }, + + // Module execution timing methods + moduleExecutionStart() { + this._moduleExecutionStart = performance.now(); + }, + + moduleExecutionStop() { + if (this._moduleExecutionStart === null) return; + const duration = performance.now() - this._moduleExecutionStart; + window.WurflRtdDebug.moduleExecutionTime = duration; + this._moduleExecutionStart = null; + }, + + // Cache read timing methods + cacheReadStart() { + this._cacheReadStart = performance.now(); + }, + + cacheReadStop() { + if (this._cacheReadStart === null) return; + const duration = performance.now() - this._cacheReadStart; + window.WurflRtdDebug.cacheReadTime = duration; + this._cacheReadStart = null; + }, + + // LCE detection timing methods + lceDetectionStart() { + this._lceDetectionStart = performance.now(); + }, + + lceDetectionStop() { + if (this._lceDetectionStart === null) return; + const duration = performance.now() - this._lceDetectionStart; + window.WurflRtdDebug.lceDetectionTime = duration; + this._lceDetectionStart = null; + }, + + // Cache write timing methods + cacheWriteStart() { + this._cacheWriteStart = performance.now(); + }, + + cacheWriteStop() { + if (this._cacheWriteStart === null) return; + const duration = performance.now() - this._cacheWriteStart; + window.WurflRtdDebug.cacheWriteTime = duration; + this._cacheWriteStart = null; + + // Calculate total WURFL.js load time (from load start to cache complete) + if (this._wurflJsLoadStart !== null) { + const totalLoadTime = performance.now() - this._wurflJsLoadStart; + window.WurflRtdDebug.wurflJsLoadTime = totalLoadTime; + this._wurflJsLoadStart = null; + } + + // Dispatch custom event when cache write data is available + if (typeof window !== 'undefined' && window.dispatchEvent) { + const event = new CustomEvent('wurflCacheWriteComplete', { + detail: { + duration: duration, + timestamp: Date.now(), + debugData: window.WurflRtdDebug + } + }); + window.dispatchEvent(event); + } + }, + + // WURFL.js load timing methods + wurflJsLoadStart() { + this._wurflJsLoadStart = performance.now(); + }, + + // Data tracking methods + setDataSource(source) { + window.WurflRtdDebug.dataSource = source; + }, + + setCacheData(wurflData, pbjsData) { + window.WurflRtdDebug.data.wurflData = wurflData; + window.WurflRtdDebug.data.pbjsData = pbjsData; + }, + + setLceData(lceDevice) { + window.WurflRtdDebug.data.lceDevice = lceDevice; + }, + + setCacheExpired(expired) { + window.WurflRtdDebug.cacheExpired = expired; + }, + + setBeaconPayload(payload) { + window.WurflRtdDebug.beaconPayload = payload; + } +}; + +// ==================== WURFL JS DEVICE MODULE ==================== +const WurflJSDevice = { + // Private properties + _wurflData: null, // WURFL data containing capability values (from window.WURFL) + _pbjsData: null, // wurfl_pbjs data with caps array and permissions (from response) + _basicCaps: null, // Cached basic capabilities (computed once) + _pubCaps: null, // Cached publisher capabilities (computed once) + _device: null, // Cached device object (computed once) + + // Constructor from WURFL.js local cache + fromCache(res) { + this._wurflData = res.WURFL || {}; + this._pbjsData = res.wurfl_pbjs || {}; + this._basicCaps = null; + this._pubCaps = null; + this._device = null; + return this; + }, + + // Private method - converts a given value to a number + _toNumber(value) { + if (value === '' || value === null) { + return undefined; + } + const num = Number(value); + return Number.isNaN(num) ? undefined : num; + }, + + // Private method - filters capabilities based on indices + _filterCaps(indexes) { + const data = {}; + const caps = this._pbjsData.caps; // Array of capability names + const wurflData = this._wurflData; // WURFL data containing capability values + + if (!indexes || !caps || !wurflData) { + return data; + } + + indexes.forEach((index) => { + const capName = caps[index]; // Get capability name by index + if (capName && capName in wurflData) { + data[capName] = wurflData[capName]; // Get value from WURFL data + } + }); + + return data; + }, + + // Private method - gets basic capabilities + _getBasicCaps() { + if (this._basicCaps !== null) { + return this._basicCaps; + } + const basicCaps = this._pbjsData.global?.basic_set?.cap_indices || []; + this._basicCaps = this._filterCaps(basicCaps); + return this._basicCaps; + }, + + // Private method - gets publisher capabilities + _getPubCaps() { + if (this._pubCaps !== null) { + return this._pubCaps; + } + const pubCaps = this._pbjsData.global?.publisher?.cap_indices || []; + this._pubCaps = this._filterCaps(pubCaps); + return this._pubCaps; + }, + + // Private method - gets bidder-specific capabilities + _getBidderCaps(bidderCode) { + const bidderCaps = this._pbjsData.bidders?.[bidderCode]?.cap_indices || []; + return this._filterCaps(bidderCaps); + }, + + // Private method - checks if bidder is authorized + _isAuthorized(bidderCode) { + return !!(this._pbjsData.bidders && bidderCode in this._pbjsData.bidders); + }, + + // Private method - checks if over quota + _isOverQuota() { + return this._pbjsData.over_quota === 1; + }, + + // Private method - returns the ortb2 device type based on WURFL data + _makeOrtb2DeviceType(wurflData) { + if (('is_ott' in wurflData) && (wurflData.is_ott)) { + return ORTB2_DEVICE_TYPE.SET_TOP_BOX; + } + if (('is_console' in wurflData) && (wurflData.is_console)) { + return ORTB2_DEVICE_TYPE.CONNECTED_DEVICE; + } + if (('physical_form_factor' in wurflData) && (wurflData.physical_form_factor === 'out_of_home_device')) { + return ORTB2_DEVICE_TYPE.OOH_DEVICE; + } + if (!('form_factor' in wurflData)) { + return undefined; + } + switch (wurflData.form_factor) { + case 'Desktop': + return ORTB2_DEVICE_TYPE.PERSONAL_COMPUTER; + case 'Smartphone': + return ORTB2_DEVICE_TYPE.PHONE; + case 'Feature Phone': + return ORTB2_DEVICE_TYPE.PHONE; + case 'Tablet': + return ORTB2_DEVICE_TYPE.TABLET; + case 'Smart-TV': + return ORTB2_DEVICE_TYPE.CONNECTED_TV; + case 'Other Non-Mobile': + return ORTB2_DEVICE_TYPE.CONNECTED_DEVICE; + case 'Other Mobile': + return ORTB2_DEVICE_TYPE.MOBILE_OR_TABLET; + default: + return undefined; + } + }, + + // Public API - returns device object for First Party Data (global) + // When under quota: returns device fields + ext.wurfl(basic+pub) + // When over quota: returns device fields only + FPD() { + if (this._device !== null) { + return this._device; + } + + const wd = this._wurflData; + if (!wd) { + this._device = {}; + return this._device; + } + + this._device = { + make: wd.brand_name, + model: wd.model_name, + devicetype: this._makeOrtb2DeviceType(wd), + os: wd.advertised_device_os, + osv: wd.advertised_device_os_version, + hwv: wd.model_name, + h: wd.resolution_height, + w: wd.resolution_width, + ppi: wd.pixel_density, + pxratio: this._toNumber(wd.density_class), + js: this._toNumber(wd.ajax_support_javascript) + }; + + const isOverQuota = this._isOverQuota(); + if (!isOverQuota) { + const basicCaps = this._getBasicCaps(); + const pubCaps = this._getPubCaps(); + this._device.ext = { + wurfl: { + ...basicCaps, + ...pubCaps + } + }; + } + + return this._device; + }, + + // Public API - returns device with bidder-specific ext data + Bidder(bidderCode) { + const isAuthorized = this._isAuthorized(bidderCode); + const isOverQuota = this._isOverQuota(); + + // When unauthorized return empty + if (!isAuthorized) { + return {}; + } + + // Start with empty device, populate only if publisher is over quota + // When over quota, we send device data to each authorized bidder individually + let fpdDevice = {}; + if (isOverQuota) { + fpdDevice = this.FPD(); + } + + if (!this._pbjsData.caps) { + return { device: fpdDevice }; + } + + // For authorized bidders: basic + pub + bidder-specific caps + const wurflData = { + ...(isOverQuota ? this._getBasicCaps() : {}), + ...this._getBidderCaps(bidderCode) + }; + + return { + device: { + ...fpdDevice, + ext: { + wurfl: wurflData + } + } + }; + } +}; +// ==================== END WURFL JS DEVICE MODULE ==================== + +// ==================== WURFL LCE DEVICE MODULE ==================== +const WurflLCEDevice = { + // Private mappings for device detection + _desktopMapping: new Map([ + ['Windows NT', 'Windows'], + ['Macintosh; Intel Mac OS X', 'macOS'], + ['Mozilla/5.0 (X11; Linux', 'Linux'], + ['X11; Ubuntu; Linux x86_64', 'Linux'], + ['Mozilla/5.0 (X11; CrOS', 'ChromeOS'], + ]), + + _tabletMapping: new Map([ + ['iPad; CPU OS ', 'iPadOS'], + ]), + + _smartphoneMapping: new Map([ + ['Android', 'Android'], + ['iPhone; CPU iPhone OS', 'iOS'], + ]), + + _smarttvMapping: new Map([ + ['Web0S', 'LG webOS'], + ['SMART-TV; Linux; Tizen', 'Tizen'], + ]), + + _ottMapping: new Map([ + ['Roku', 'Roku OS'], + ['Xbox', 'Windows'], + ['PLAYSTATION', 'PlayStation OS'], + ['PlayStation', 'PlayStation OS'], + ]), + + _makeMapping: new Map([ + ['motorola', 'Motorola'], + [' moto ', 'Motorola'], + ['Android', 'Generic'], + ['iPad', 'Apple'], + ['iPhone', 'Apple'], + ['Firefox', 'Mozilla'], + ['Edge', 'Microsoft'], + ['Chrome', 'Google'], + ]), + + _modelMapping: new Map([ + ['Android', 'Android'], + ['iPad', 'iPad'], + ['iPhone', 'iPhone'], + ['Firefox', 'Firefox'], + ['Edge', 'Edge'], + ['Chrome', 'Chrome'], + ]), + + // Private helper methods + _parseOsVersion(ua, osName) { + switch (osName) { + case 'Windows': { + const matches = ua.match(/Windows NT ([\d.]+)/); + return matches ? matches[1] : ''; + } + case 'macOS': { + const matches = ua.match(/Mac OS X ([\d_]+)/); + return matches ? matches[1].replace(/_/g, '.') : ''; + } + case 'iOS': { + // iOS 26 specific logic + const matches1 = ua.match(/iPhone; CPU iPhone OS 18[\d_]+ like Mac OS X\).+(?:Version|FBSV)\/(26[\d.]+)/); + if (matches1) { + return matches1[1]; + } + // iOS 18.x and lower + const matches2 = ua.match(/iPhone; CPU iPhone OS ([\d_]+) like Mac OS X/); + return matches2 ? matches2[1].replace(/_/g, '.') : ''; + } + case 'iPadOS': { + // iOS 26 specific logic + const matches1 = ua.match(/iPad; CPU OS 18[\d_]+ like Mac OS X\).+(?:Version|FBSV)\/(26[\d.]+)/); + if (matches1) { + return matches1[1]; + } + // iOS 18.x and lower + const matches2 = ua.match(/iPad; CPU OS ([\d_]+) like Mac OS X/); + return matches2 ? matches2[1].replace(/_/g, '.') : ''; + } + case 'Android': { + // For Android UAs with a decimal + const matches1 = ua.match(/Android ([\d.]+)/); + if (matches1) { + return matches1[1]; + } + // For Android UAs without a decimal + const matches2 = ua.match(/Android ([\d]+)/); + return matches2 ? matches2[1] : ''; + } + case 'ChromeOS': { + const matches = ua.match(/CrOS x86_64 ([\d.]+)/); + return matches ? matches[1] : ''; + } + case 'Tizen': { + const matches = ua.match(/Tizen ([\d.]+)/); + return matches ? matches[1] : ''; + } + case 'Roku OS': { + const matches = ua.match(/Roku\/DVP [\dA-Z]+ [\d.]+\/([\d.]+)/); + return matches ? matches[1] : ''; + } + case 'PlayStation OS': { + // PS4 + const matches1 = ua.match(/PlayStation \d\/([\d.]+)/); + if (matches1) { + return matches1[1]; + } + // PS3 + const matches2 = ua.match(/PLAYSTATION \d ([\d.]+)/); + return matches2 ? matches2[1] : ''; + } + case 'Linux': + case 'LG webOS': + default: + return ''; + } + }, + + _makeDeviceInfo(deviceType, osName, ua) { + return { deviceType, osName, osVersion: this._parseOsVersion(ua, osName) }; + }, + + _getDeviceInfo(ua) { + // Iterate over ottMapping + // Should remove above Desktop + for (const [osToken, osName] of this._ottMapping) { + if (ua.includes(osToken)) { + return this._makeDeviceInfo(ORTB2_DEVICE_TYPE.SET_TOP_BOX, osName, ua); + } + } + // Iterate over desktopMapping + for (const [osToken, osName] of this._desktopMapping) { + if (ua.includes(osToken)) { + return this._makeDeviceInfo(ORTB2_DEVICE_TYPE.PERSONAL_COMPUTER, osName, ua); + } + } + // Iterate over tabletMapping + for (const [osToken, osName] of this._tabletMapping) { + if (ua.includes(osToken)) { + return this._makeDeviceInfo(ORTB2_DEVICE_TYPE.TABLET, osName, ua); + } + } + // Android Tablets + if (ua.includes('Android') && !ua.includes('Mobile Safari') && ua.includes('Safari')) { + return this._makeDeviceInfo(ORTB2_DEVICE_TYPE.TABLET, 'Android', ua); + } + // Iterate over smartphoneMapping + for (const [osToken, osName] of this._smartphoneMapping) { + if (ua.includes(osToken)) { + return this._makeDeviceInfo(ORTB2_DEVICE_TYPE.PHONE, osName, ua); + } + } + // Iterate over smarttvMapping + for (const [osToken, osName] of this._smarttvMapping) { + if (ua.includes(osToken)) { + return this._makeDeviceInfo(ORTB2_DEVICE_TYPE.CONNECTED_TV, osName, ua); + } + } + return { deviceType: '', osName: '', osVersion: '' }; + }, + + _getDevicePixelRatioValue(osName) { + switch (osName) { + case 'Android': + return 2.0; + case 'iOS': + return 3.0; + case 'iPadOS': + return 2.0; + default: + return 1.0; + } + }, + + _getMake(ua) { + for (const [makeToken, brandName] of this._makeMapping) { + if (ua.includes(makeToken)) { + return brandName; + } + } + return 'Generic'; + }, + + _getModel(ua) { + for (const [modelToken, modelName] of this._modelMapping) { + if (ua.includes(modelToken)) { + return modelName; + } + } + return ''; + }, + + _getUserAgent() { + return window.navigator?.userAgent || ''; + }, + + _isRobot(useragent) { + const botTokens = ['+http', 'Googlebot', 'BingPreview', 'Yahoo! Slurp']; + for (const botToken of botTokens) { + if (useragent.includes(botToken)) { + return true; + } + } + return false; + }, + + // Public API - returns device object for First Party Data (global) + FPD() { + // Early exit - check window exists + if (typeof window === 'undefined') { + return { js: 1 }; + } + + const device = { js: 1 }; + const useragent = this._getUserAgent(); + + // Only process UA-dependent properties if we have a UA + if (useragent) { + // Get device info + const deviceInfo = this._getDeviceInfo(useragent); + if (deviceInfo.deviceType !== undefined) { + device.devicetype = deviceInfo.deviceType; + } + if (deviceInfo.osName !== undefined) { + device.os = deviceInfo.osName; + } + if (deviceInfo.osVersion !== undefined) { + device.osv = deviceInfo.osVersion; + } + + // Make/model + const make = this._getMake(useragent); + if (make !== undefined) { + device.make = make; + } + + const model = this._getModel(useragent); + if (model !== undefined) { + device.model = model; + device.hwv = model; + } + + // Device pixel ratio based on OS + const pixelRatio = this._getDevicePixelRatioValue(deviceInfo.osName); + if (pixelRatio !== undefined) { + device.pxratio = pixelRatio; + } + } + + // Add ext.wurfl with is_robot detection + if (useragent) { + device.ext = { + wurfl: { + is_robot: this._isRobot(useragent) + } + }; + } + + return device; + } +}; +// ==================== END WURFL LCE DEVICE MODULE ==================== + +// ==================== A/B TEST MANAGER ==================== + +const ABTestManager = { + _enabled: false, + _name: null, + _variant: null, + _excludeLCE: true, + _enrichmentType: null, + + /** + * Initializes A/B test configuration + * @param {Object} params Configuration params from config.params + */ + init(params) { + this._enabled = false; + this._name = null; + this._variant = null; + this._excludeLCE = true; + this._enrichmentType = null; + + const abTestEnabled = params?.abTest ?? false; + if (!abTestEnabled) { + return; + } + + this._enabled = true; + this._name = params?.abName ?? AB_TEST.DEFAULT_NAME; + this._excludeLCE = params?.abExcludeLCE ?? true; + + const split = params?.abSplit ?? AB_TEST.DEFAULT_SPLIT; + this._variant = this._computeVariant(split); + + logger.logMessage(`A/B test "${this._name}": user in ${this._variant} group (exclude_lce: ${this._excludeLCE})`); + }, + + /** + * _computeVariant determines A/B test variant assignment based on split + * @param {number} split Treatment group split from 0-1 (float, e.g., 0.5 = 50% treatment) + * @returns {string} AB_TEST.TREATMENT_GROUP or AB_TEST.CONTROL_GROUP + */ + _computeVariant(split) { + if (split >= 1) { + return AB_TEST.TREATMENT_GROUP; + } + if (split <= 0) { + return AB_TEST.CONTROL_GROUP; + } + return Math.random() < split ? AB_TEST.TREATMENT_GROUP : AB_TEST.CONTROL_GROUP; + }, + + /** + * Sets the enrichment type encountered in current auction + * @param {string} enrichmentType 'lce' or 'wurfl' + */ + setEnrichmentType(enrichmentType) { + this._enrichmentType = enrichmentType; + }, + + /** + * Checks if A/B test is enabled for current auction + * @returns {boolean} True if A/B test should be applied + */ + isEnabled() { + if (!this._enabled) return false; + if (this._enrichmentType === AB_TEST.ENRICHMENT_TYPE_LCE && this._excludeLCE) { + return false; + } + return true; + }, + + /** + * Checks if enrichment should be skipped (control group) + * @returns {boolean} True if enrichment should be skipped + */ + isInControlGroup() { + if (!this.isEnabled()) { + return false; + } + return (this._variant === AB_TEST.CONTROL_GROUP) + }, + + /** + * Gets beacon payload fields (returns null if not active for auction) + * @returns {Object|null} + */ + getBeaconPayload() { + if (!this.isEnabled()) { + return null; + } + + return { + ab_name: this._name, + ab_variant: this._variant + }; + } +}; + +// ==================== END A/B TEST MANAGER MODULE ==================== + +// ==================== EXPORTED FUNCTIONS ==================== + +/** + * init initializes the WURFL RTD submodule + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +const init = (config, userConsent) => { + // Initialize debugger based on global debug flag + WurflDebugger.init(); + + // Initialize module state + bidderEnrichment = new Map(); + enrichmentType = ENRICHMENT_TYPE.UNKNOWN; + wurflId = ''; + samplingRate = DEFAULT_SAMPLING_RATE; + tier = ''; + overQuota = DEFAULT_OVER_QUOTA; + + logger.logMessage('initialized', { version: MODULE_VERSION }); + + // A/B testing: initialize ABTestManager + ABTestManager.init(config?.params); + + return true; } /** - * enrichOrtb2DeviceData enriches the ortb2data device data with WURFL data. - * Note: it does not overrides properties set by Prebid.js - * @param {String} key the device property key - * @param {any} value the value of the device property - * @param {Object} device the ortb2 device object from Prebid.js - * @param {Object} ortb2data the ortb2 device data enrchiced with WURFL data + * 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 */ -function enrichOrtb2DeviceData(key, value, device, ortb2data) { - if (device?.[key] !== undefined) { - // value already defined by Prebid.js, do not overrides +const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => { + // Start module execution timing + WurflDebugger.moduleExecutionStart(); + + // Extract bidders from request configuration and set default enrichment + const bidders = new Set(); + reqBidsConfigObj.adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + bidders.add(bid.bidder); + bidderEnrichment.set(bid.bidder, ENRICHMENT_TYPE.UNKNOWN); + }); + }); + + // Determine enrichment type based on cache availability + WurflDebugger.cacheReadStart(); + const cachedWurflData = getObjectFromStorage(WURFL_RTD_STORAGE_KEY); + WurflDebugger.cacheReadStop(); + + const abEnrichmentType = cachedWurflData ? AB_TEST.ENRICHMENT_TYPE_WURFL : AB_TEST.ENRICHMENT_TYPE_LCE; + ABTestManager.setEnrichmentType(abEnrichmentType); + + // A/B test: Skip enrichment for control group + if (ABTestManager.isInControlGroup()) { + logger.logMessage('A/B test control group: skipping enrichment'); + enrichmentType = ENRICHMENT_TYPE.NONE; + bidders.forEach(bidder => bidderEnrichment.set(bidder, ENRICHMENT_TYPE.NONE)); + WurflDebugger.moduleExecutionStop(); + callback(); return; } - if (value === undefined) { + + // Priority 1: Check if WURFL.js response is cached + if (cachedWurflData) { + const isExpired = cachedWurflData.expire_at && Date.now() > cachedWurflData.expire_at; + + WurflDebugger.setDataSource('cache'); + WurflDebugger.setCacheExpired(isExpired); + WurflDebugger.setCacheData(cachedWurflData.WURFL, cachedWurflData.wurfl_pbjs); + + const wjsDevice = WurflJSDevice.fromCache(cachedWurflData); + if (wjsDevice._isOverQuota()) { + enrichmentType = ENRICHMENT_TYPE.NONE; + } else { + enrichDeviceFPD(reqBidsConfigObj, wjsDevice.FPD()); + enrichmentType = ENRICHMENT_TYPE.WURFL_PUB; + } + enrichDeviceBidder(reqBidsConfigObj, bidders, wjsDevice); + + // Store WURFL ID for analytics + wurflId = cachedWurflData.WURFL?.wurfl_id || ''; + + // Store sampling rate for beacon + samplingRate = cachedWurflData.wurfl_pbjs?.sampling_rate ?? DEFAULT_SAMPLING_RATE; + + // Store tier for beacon + tier = cachedWurflData.wurfl_pbjs?.tier ?? ''; + + // Store over_quota for beacon + overQuota = cachedWurflData.wurfl_pbjs?.over_quota ?? DEFAULT_OVER_QUOTA; + + // If expired, refresh cache async + if (isExpired) { + loadWurflJsAsync(config, bidders); + } + + logger.logMessage('enrichment completed', { + type: enrichmentType, + dataSource: 'cache', + cacheExpired: isExpired, + bidders: Object.fromEntries(bidderEnrichment), + totalBidders: bidderEnrichment.size + }); + + WurflDebugger.moduleExecutionStop(); + callback(); return; } - ortb2data.device[key] = value; -} -/** - * toNumber converts a given value to a number. - * Returns `undefined` if the conversion results in `NaN`. - * @param {any} value - The value to convert to a number. - * @returns {number|undefined} The converted number, or `undefined` if the conversion fails. - */ -export function toNumber(value) { - if (value === '' || value === null) { - return undefined; + // Priority 2: return LCE data + WurflDebugger.setDataSource('lce'); + WurflDebugger.lceDetectionStart(); + + let lceDevice; + try { + lceDevice = WurflLCEDevice.FPD(); + enrichmentType = ENRICHMENT_TYPE.LCE; + } catch (e) { + logger.logError('Error generating LCE device data:', e); + lceDevice = { js: 1 }; + enrichmentType = ENRICHMENT_TYPE.LCE_ERROR; } - const num = Number(value); - return Number.isNaN(num) ? undefined : num; + + WurflDebugger.lceDetectionStop(); + WurflDebugger.setLceData(lceDevice); + enrichDeviceFPD(reqBidsConfigObj, lceDevice); + + // Set enrichment type for all bidders + bidders.forEach(bidder => bidderEnrichment.set(bidder, enrichmentType)); + + // Set default sampling rate for LCE + samplingRate = DEFAULT_SAMPLING_RATE; + + // Set default tier for LCE + tier = ''; + + // Set default over_quota for LCE + overQuota = DEFAULT_OVER_QUOTA; + + // Load WURFL.js async for future requests + loadWurflJsAsync(config, bidders); + + logger.logMessage('enrichment completed', { + type: enrichmentType, + dataSource: 'lce', + bidders: Object.fromEntries(bidderEnrichment), + totalBidders: bidderEnrichment.size + }); + + WurflDebugger.moduleExecutionStop(); + callback(); } /** @@ -266,23 +1279,135 @@ export function toNumber(value) { * @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; + // Apply sampling + if (!shouldSample(samplingRate)) { + logger.logMessage(`beacon skipped due to sampling (rate: ${samplingRate}%)`); + return; } - const url = new URL(host); - url.pathname = STATS_ENDPOINT_PATH; + const statsHost = config.params?.statsHost ?? null; + + let host = STATS_HOST; + if (statsHost) { + host = statsHost; + } - if (enrichedBidders.size === 0) { + let url; + try { + url = new URL(host); + url.pathname = STATS_ENDPOINT_PATH; + } catch (e) { + logger.logError('Invalid stats host URL:', host); return; } - var payload = JSON.stringify({ bidders: [...enrichedBidders] }); + // Calculate consent class + let consentClass; + try { + consentClass = getConsentClass(userConsent); + logger.logMessage('consent class', consentClass); + } catch (e) { + logger.logError('Error calculating consent class:', e); + consentClass = CONSENT_CLASS.ERROR; + } + + // Build a lookup object for winning bid request IDs + const winningBids = getGlobal().getHighestCpmBids() || []; + const winningBidIds = {}; + for (let i = 0; i < winningBids.length; i++) { + const bid = winningBids[i]; + winningBidIds[bid.requestId] = true; + } + + // Build a lookup object for bid responses: "adUnitCode:bidderCode" -> bid + const bidResponseMap = {}; + const bidsReceived = auctionDetails.bidsReceived || []; + for (let i = 0; i < bidsReceived.length; i++) { + const bid = bidsReceived[i]; + const adUnitCode = bid.adUnitCode; + const bidderCode = bid.bidder || bid.bidderCode; + const key = adUnitCode + ':' + bidderCode; + bidResponseMap[key] = bid; + } + + // Build ad units array with all bidders (including non-responders) + const adUnits = []; + + if (auctionDetails.adUnits) { + for (let i = 0; i < auctionDetails.adUnits.length; i++) { + const adUnit = auctionDetails.adUnits[i]; + const adUnitCode = adUnit.code; + const bidders = []; + + // Check each bidder configured for this ad unit + const bids = adUnit.bids || []; + for (let j = 0; j < bids.length; j++) { + const bidConfig = bids[j]; + const bidderCode = bidConfig.bidder; + const key = adUnitCode + ':' + bidderCode; + const bidResponse = bidResponseMap[key]; + + if (bidResponse) { + // Bidder responded - include full data + const isWinner = winningBidIds[bidResponse.requestId] === true; + bidders.push({ + bidder: bidderCode, + bdr_enrich: bidderEnrichment.get(bidderCode), + cpm: bidResponse.cpm, + currency: bidResponse.currency, + won: isWinner + }); + } else { + // Bidder didn't respond - include without cpm/currency + bidders.push({ + bidder: bidderCode, + bdr_enrich: bidderEnrichment.get(bidderCode), + won: false + }); + } + } + + adUnits.push({ + ad_unit_code: adUnitCode, + bidders: bidders + }); + } + } + + logger.logMessage('auction completed', { + bidsReceived: auctionDetails.bidsReceived ? auctionDetails.bidsReceived.length : 0, + bidsWon: winningBids.length, + adUnits: adUnits.length + }); + + // Build complete payload + const payloadData = { + version: MODULE_VERSION, + domain: typeof window !== 'undefined' ? window.location.hostname : '', + path: typeof window !== 'undefined' ? window.location.pathname : '', + sampling_rate: samplingRate, + enrichment: enrichmentType, + wurfl_id: wurflId, + tier: tier, + over_quota: overQuota, + consent_class: consentClass, + ad_units: adUnits + }; + + // Add A/B test fields if enabled + const abPayload = ABTestManager.getBeaconPayload(); + if (abPayload) { + payloadData.ab_name = abPayload.ab_name; + payloadData.ab_variant = abPayload.ab_variant; + } + + const payload = JSON.stringify(payloadData); + + // Both sendBeacon and fetch send as text/plain to avoid CORS preflight requests. + // Server must parse body as JSON regardless of Content-Type header. const sentBeacon = sendBeacon(url.toString(), payload); if (sentBeacon) { + WurflDebugger.setBeaconPayload(payloadData); return; } @@ -291,9 +1416,15 @@ function onAuctionEndEvent(auctionDetails, config, userConsent) { body: payload, mode: 'no-cors', keepalive: true + }).catch((e) => { + logger.logError('Failed to send beacon via fetch:', e); }); + + WurflDebugger.setBeaconPayload(payloadData); } +// ==================== MODULE EXPORT ==================== + // The WURFL submodule export const wurflSubmodule = { name: MODULE_NAME, diff --git a/modules/wurflRtdProvider.md b/modules/wurflRtdProvider.md index abee06c02e7..4bc088303e0 100644 --- a/modules/wurflRtdProvider.md +++ b/modules/wurflRtdProvider.md @@ -8,10 +8,13 @@ ## 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 capabilities like `is_mobile`, `complete_device_name` and `form_factor`, and the `wurfl_id`. +The WURFL RTD module enriches Prebid.js bid requests with comprehensive device detection data. -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). +The WURFL RTD module relies on localStorage caching and local client-side detection, providing instant device enrichment on every page load. + +The module enriches `ortb2.device` with complete device information and adds extended WURFL capabilities to `device.ext.wurfl`, ensuring all bidder adapters have immediate access to enriched device data. + +**Note:** This module loads a dynamically generated JavaScript from prebid.wurflcloud.com ## User-Agent Client Hints @@ -20,6 +23,7 @@ WURFL.js is fully compatible with Chromium's User-Agent Client Hints (UA-CH) ini ## Usage ### Build + ``` gulp build --modules="wurflRtdProvider,appnexusBidAdapter,..." ``` @@ -31,30 +35,53 @@ Use `setConfig` to instruct Prebid.js to initilize the WURFL RTD module, as spec 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 - } - }] - } + realTimeData: { + dataProviders: [ + { + name: "wurfl", + }, + ], + }, }); ``` ### 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` | +| Name | Type | Description | Default | +| :------------- | :------ | :----------------------------------------- | :------------- | +| name | String | Real time data module name | Always 'wurfl' | +| params | Object | | | +| params.altHost | String | Alternate host to connect to WURFL.js | | +| params.abTest | Boolean | Enable A/B testing mode | `false` | +| params.abName | String | A/B test name identifier | `'unknown'` | +| params.abSplit | Number | Fraction of users in treatment group (0-1) | `0.5` | + +### A/B Testing + +The WURFL RTD module supports A/B testing to measure the impact of WURFL enrichment on ad performance: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [ + { + name: "wurfl", + params: { + abTest: true, + abName: "pub_test", + abSplit: 0.75, // 75% treatment, 25% control + }, + }, + ], + }, +}); +``` + +- **Treatment group** (`abSplit` \* 100%): Module enabled, bid requests enriched with WURFL device data +- **Control group** ((1 - `abSplit`) \* 100%): Module disabled, no enrichment occurs +- Assignment is random on each page load based on `Math.random()` +- Example: `abSplit: 0.75` means 75% get WURFL enrichment, 25% don't ## Testing diff --git a/modules/yahooAdsBidAdapter.js b/modules/yahooAdsBidAdapter.js index caed513aa6a..16c9365b5d7 100644 --- a/modules/yahooAdsBidAdapter.js +++ b/modules/yahooAdsBidAdapter.js @@ -288,6 +288,7 @@ function generateOpenRtbObject(bidderRequest, bid) { } }, source: { + tid: bidderRequest.ortb2?.source?.tid, ext: { hb: 1, adapterver: ADAPTER_VERSION, @@ -490,11 +491,16 @@ function appendFirstPartyData(outBoundBidRequest, bid) { const allowedUserStrings = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; const allowedUserNumbers = ['yob']; const allowedUserArrays = ['data']; - const allowedUserObjects = ['ext']; outBoundBidRequest.user = validateAppendObject('string', allowedUserStrings, userObject, outBoundBidRequest.user); outBoundBidRequest.user = validateAppendObject('number', allowedUserNumbers, userObject, outBoundBidRequest.user); outBoundBidRequest.user = validateAppendObject('array', allowedUserArrays, userObject, outBoundBidRequest.user); - outBoundBidRequest.user.ext = validateAppendObject('object', allowedUserObjects, userObject, outBoundBidRequest.user.ext); + // Merge ext properties from ortb2.user.ext into existing user.ext instead of nesting + if (userObject.ext && isPlainObject(userObject.ext)) { + outBoundBidRequest.user.ext = { + ...outBoundBidRequest.user.ext, + ...userObject.ext + }; + } }; return outBoundBidRequest; diff --git a/modules/yahooAdsBidAdapter.md b/modules/yahooAdsBidAdapter.md index df9b71b2314..a26d24338c5 100644 --- a/modules/yahooAdsBidAdapter.md +++ b/modules/yahooAdsBidAdapter.md @@ -18,6 +18,7 @@ The Yahoo Advertising Bid Adapter is an OpenRTB interface that consolidates all * User ID Modules - ConnectId and others * First Party Data (ortb2 & ortb2Imp) * Custom TTL (time to live) +* Transaction ID (TID) support via ortb2.source.tid # Adapter Aliases Whilst the primary bidder code for this bid adapter is `yahooAds`, the aliases `yahoossp` and `yahooAdvertising` can be used to enable this adapter. If you wish to set Prebid configuration specifically for this bid adapter, then the configuration key _must_ match the used bidder code. All examples in this documentation use the primiry bidder code, but switching `yahooAds` with one of the relevant aliases may be required for your setup. Let's take [setting the request mode](#adapter-request-mode) as an example; if you used the `yahoossp` alias, then the corresponding `setConfig` API call would look like this: @@ -562,6 +563,40 @@ const adUnits = [{ ] ``` +## Transaction ID (TID) Support +The Yahoo Advertising bid adapter supports reading publisher-provided transaction IDs from `ortb2.source.tid` and including them in the OpenRTB request. This enables better bid request tracking and deduplication across the supply chain. + +**Important:** To use transaction IDs, you must enable TIDs in your Prebid configuration by setting `enableTIDs: true`. + +### Global Transaction ID (applies to all bidders) +```javascript +pbjs.setConfig({ + enableTIDs: true, // Required for TID support + ortb2: { + source: { + tid: "transaction-id-12345" + } + } +}); +``` + +### Bidder-Specific Transaction ID (Yahoo Ads only) +```javascript +pbjs.setBidderConfig({ + bidders: ['yahooAds'], + config: { + enableTIDs: true, // Required for TID support + ortb2: { + source: { + tid: "yahoo-specific-tid-67890" + } + } + } +}); +``` + +**Note:** If `enableTIDs` is not set to `true`, the transaction ID will not be available to the adapter, even if `ortb2.source.tid` is configured. When TID is not provided or not enabled, the adapter will not include the `source.tid` field in the OpenRTB request. + # Optional: Bidder bidOverride Parameters The Yahoo Advertising bid adapter allows passing override data to the outbound bid-request that overrides First Party Data. **Important!** We highly recommend using prebid modules to pass data instead of bidder speicifc overrides. diff --git a/modules/yaleoBidAdapter.md b/modules/yaleoBidAdapter.md new file mode 100644 index 00000000000..505d3617fd5 --- /dev/null +++ b/modules/yaleoBidAdapter.md @@ -0,0 +1,39 @@ +# Yaleo Bid Adapter + +# Overview + +``` +Module name: Yaleo Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandr.kim@audienzz.com +``` + +# Description + +Module that connects to Yaleo's demand sources. + +**Note:** the bid adapter requires correct setup and approval. For more information visit [yaleo.com](https://www.yaleo.com) or contact [hola@yaleo.com](mailto:hola@yaleo.com). + +# Test parameters + +**Note:** to receive bids when testing without proper integration with the demand source, enable Prebid.js debug mode. See [how to enable debug mode](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#debugging) for details. + +```js +const adUnits = [ + { + code: "test-div-1", + mediaTypes: { + banner: { + sizes: [[300, 300], [300, 600]], + } + }, + bids: [{ + bidder: "yaleo", + params: { + placementId: "95a09f24-afb8-441c-977b-08b4039cb88e", + } + }] + } +]; +``` + diff --git a/modules/yaleoBidAdapter.ts b/modules/yaleoBidAdapter.ts new file mode 100755 index 00000000000..980d5634df7 --- /dev/null +++ b/modules/yaleoBidAdapter.ts @@ -0,0 +1,80 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; +import { BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +interface YaleoBidParams { + /** + * Yaleo placement ID. + */ + placementId: string; + /** + * Member ID. + * @default 3927 + */ + memberId?: number; + /** + * Maximum CPM value. Bids with a CPM higher than the specified value will be rejected. + */ + maxCpm?: number; +} + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: YaleoBidParams; + } +} + +const BIDDER_CODE = 'yaleo'; +const AUDIENZZ_VENDOR_ID = 783; +const PREBID_URL = 'https://bidder.yaleo.com/prebid'; +const DEFAULT_TTL = 300; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_TTL, + }, + processors: pbsExtensions, +}); + +const isBidRequestValid: BidderSpec['isBidRequestValid'] = (request) => { + if (!request.params || typeof request.params.placementId !== 'string') { + return false; + } + + return !!request.params.placementId; +}; + +const buildRequests: BidderSpec['buildRequests'] = (validBidRequests, bidderRequest) => { + const ortbRequest = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + }); + + return { + url: PREBID_URL, + method: 'POST', + data: ortbRequest, + }; +} + +const interpretResponse: BidderSpec['interpretResponse'] = (serverResponse, bidderRequest) => { + const response = converter.fromORTB({ + response: serverResponse.body, + request: bidderRequest.data, + }); + + return response; +}; + +export const spec: BidderSpec = { + buildRequests, + code: BIDDER_CODE, + gvlid: AUDIENZZ_VENDOR_ID, + interpretResponse, + isBidRequestValid, + supportedMediaTypes: [BANNER], +}; + +registerBidder(spec); diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md index ac454285806..9a7684d2644 100644 --- a/modules/yandexBidAdapter.md +++ b/modules/yandexBidAdapter.md @@ -8,40 +8,42 @@ Maintainer: prebid@yandex-team.com # Description -The Yandex Prebid Adapter is designed for seamless integration with Yandex's advertising services. It facilitates effective bidding by leveraging Yandex's robust ad-serving technology, ensuring publishers can maximize their ad revenue through efficient and targeted ad placements. +The Yandex Prebid Adapter is designed for seamless integration with Yandex's advertising services. It facilitates effective bidding by leveraging Yandex's robust ad-serving technology, ensuring publishers can maximize their ad revenue through efficient and targeted ad placements. Please reach out to for the integration guide and more details. For comprehensive auction analytics, consider using the [Yandex Analytics Adapter](https://docs.prebid.org/dev-docs/analytics/yandex.html). This tool provides essential insights into auction dynamics and user interactions, empowering publishers to fine-tune their strategies for optimal ad performance. # Parameters -| Name | Required? | Description | Example | Type | -|---------------|--------------------------------------------|-------------|---------|-----------| -| `placementId` | Yes | Block ID | `123-1` | `String` | -| `pageId` | No
Deprecated. Please use `placementId` | Page ID | `123` | `Integer` | -| `impId` | No
Deprecated. Please use `placementId` | Imp ID | `1` | `Integer` | +| Name | Scope | Description | Example | Type | +|---------------|----------------------------------------|--------------|------------------|-----------| +| `placementId` | Required | Placement ID | `'R-X-123456-1'` | `String` | +| `cur` | Optional. Default value is `'EUR'` | Bid Currency | `'USD'` | `String` | +| `pageId` | `Deprecated`. Please use `placementId` | Page ID | `123` | `Integer` | +| `impId` | `Deprecated`. Please use `placementId` | Imp ID | `1` | `Integer` | # Test Parameters ```javascript var adUnits = [ - { // banner + { // banner example. please check if the 'placementId' is active in Yandex UI code: 'banner-1', mediaTypes: { banner: { - sizes: [[240, 400], [300, 600]], + sizes: [[300, 250], [300, 600]], } }, bids: [ { bidder: 'yandex', params: { - placementId: '346580-1' + placementId: 'R-A-346580-1', + cur: 'USD' }, } ], }, - { // video - code: 'banner-2', + { // video example. please check if the 'placementId' is active in Yandex UI + code: 'video-1', mediaTypes: { video: { sizes: [[640, 480]], @@ -57,13 +59,14 @@ var adUnits = [ { bidder: 'yandex', params: { - placementId: '346580-1' + placementId: 'R-V-346580-1', + cur: 'USD' }, } ], }, - { // native - code: 'banner-3',, + { // native example. please check if the 'placementId' is active in Yandex UI + code: 'native-1', mediaTypes: { native: { title: { @@ -84,7 +87,7 @@ var adUnits = [ len: 90 }, sponsoredBy: { - len: 25, + len: 25 } }, }, @@ -92,7 +95,8 @@ var adUnits = [ { bidder: 'yandex', params: { - placementId: '346580-1' + placementId: 'R-A-346580-2', + cur: 'USD' }, } ], diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index f606194e132..18926496258 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -15,9 +15,9 @@ import { parseQueryStringParameters, parseUrl } from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {Renderer} from '../src/Renderer.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -42,8 +42,6 @@ const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'plcmt', 's 'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable']; const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; const LOCAL_WINDOW = getWindowTop(); -const DEFAULT_PLAYBACK_METHOD = 2; -const DEFAULT_START_DELAY = 0; const VAST_TIMEOUT = 15000; const MAX_BANNER_REQUEST_URL_LENGTH = 8000; const BANNER_REQUEST_PROPERTIES_TO_REDUCE = ['description', 'title', 'pr', 'page_url']; @@ -96,7 +94,8 @@ export const spec = { cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '', gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || '', gpp_sid: - deepAccess(bidderRequest, 'gppConsent.applicableSections') || []}), + deepAccess(bidderRequest, 'gppConsent.applicableSections') || [] + }), us_privacy: deepAccess(bidderRequest, 'uspConsent') || '', }; if (topicsData) { @@ -211,7 +210,7 @@ export const spec = { return bids; }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { const syncs = []; const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; @@ -505,10 +504,6 @@ function openRtbImpression(bidRequest) { imp.video.skip = 1; delete imp.video.skippable; } - if (imp.video.plcmt !== 1 || imp.video.placement !== 1) { - imp.video.startdelay = DEFAULT_START_DELAY; - imp.video.playbackmethod = [ DEFAULT_PLAYBACK_METHOD ]; - } if (gpid) { imp.ext.gpid = gpid; } @@ -740,7 +735,7 @@ function canAccessTopWindow() { } function isStage(bidderRequest) { - return !!bidderRequest.refererInfo?.referer?.includes('pb_force_a'); + return !!bidderRequest.refererInfo?.page?.includes('pb_force_a'); } function getAdserverUrl(path, stage) { diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index c71dadd1573..244da9aa3a1 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -126,6 +126,12 @@ export const spec = { payload.gpid = gpid; } + // instl + const instl = deepAccess(bidRequest, 'ortb2Imp.instl'); + if (instl === 1 || instl === '1') { + payload.instl = 1; + } + return { method: 'GET', url: ENDPOINT_URL, diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index ed4971d39a7..46e7f9d5951 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -6,6 +6,7 @@ import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import {config} from '../src/config.js'; import {parseDomain} from '../src/refererDetection.js'; +import {BANNER, VIDEO} from "../src/mediaTypes.js"; const ZETA_GVL_ID = 833; const ADAPTER_CODE = 'zeta_global_ssp'; @@ -40,12 +41,14 @@ function adRenderSucceededHandler(args) { auctionId: args.bid?.auctionId, creativeId: args.bid?.creativeId, bidder: args.bid?.bidderCode, + dspId: args.bid?.dspId, mediaType: args.bid?.mediaType, size: args.bid?.size, adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, cpm: args.bid?.cpm, - adUnitCode: args.bid?.adUnitCode + adUnitCode: args.bid?.adUnitCode, + floorData: args.bid?.floorData }, device: { ua: navigator.userAgent @@ -61,15 +64,35 @@ function auctionEndHandler(args) { bidderCode: br?.bidderCode, domain: br?.refererInfo?.domain, page: br?.refererInfo?.page, - bids: br?.bids?.map(b => ({ - bidId: b?.bidId, - auctionId: b?.auctionId, - bidder: b?.bidder, - 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 - })) + bids: br?.bids?.map(b => { + const mediaType = b?.mediaTypes?.video ? VIDEO : (b?.mediaTypes?.banner ? BANNER : undefined); + let floor; + if (typeof b?.getFloor === 'function') { + try { + const floorInfo = b.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + if (floorInfo && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (e) { + // ignore floor lookup errors + } + } + + return { + bidId: b?.bidId, + auctionId: b?.auctionId, + bidder: b?.bidder, + mediaType: mediaType, + sizes: b?.sizes, + device: b?.ortb2?.device, + adUnitCode: b?.adUnitCode, + floor: floor + }; + }) })), bidsReceived: args.bidsReceived?.map(br => ({ adId: br?.adId, @@ -81,7 +104,8 @@ function auctionEndHandler(args) { adomain: br?.adserverTargeting?.hb_adomain, timeToRespond: br?.timeToRespond, cpm: br?.cpm, - adUnitCode: br?.adUnitCode + adUnitCode: br?.adUnitCode, + dspId: br?.dspId })) } sendEvent(EVENTS.AUCTION_END, event); @@ -92,16 +116,35 @@ function bidTimeoutHandler(args) { zetaParams: zetaParams, domain: args.find(t => t?.ortb2?.site?.domain)?.ortb2?.site?.domain, page: args.find(t => t?.ortb2?.site?.page)?.ortb2?.site?.page, - timeouts: args.map(t => ({ - bidId: t?.bidId, - auctionId: t?.auctionId, - bidder: t?.bidder, - mediaType: t?.mediaTypes?.video ? 'VIDEO' : (t?.mediaTypes?.banner ? 'BANNER' : undefined), - size: t?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), - timeout: t?.timeout, - device: t?.ortb2?.device, - adUnitCode: t?.adUnitCode - })) + timeouts: args.map(t => { + const mediaType = t?.mediaTypes?.video ? VIDEO : (t?.mediaTypes?.banner ? BANNER : undefined); + let floor; + if (typeof t?.getFloor === 'function') { + try { + const floorInfo = t.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + if (floorInfo && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (e) { + // ignore floor lookup errors + } + } + return { + bidId: t?.bidId, + auctionId: t?.auctionId, + bidder: t?.bidder, + mediaType: mediaType, + sizes: t?.sizes, + timeout: t?.timeout, + device: t?.ortb2?.device, + adUnitCode: t?.adUnitCode, + floor: floor + } + }) } sendEvent(EVENTS.BID_TIMEOUT, event); } diff --git a/package-lock.json b/package-lock.json index 2da5c893b64..85fc646f8bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "prebid.js", - "version": "10.18.0-pre", - "lockfileVersion": 3, + "version": "10.28.0-pre", + "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.18.0-pre", + "version": "10.28.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", - "@babel/plugin-transform-runtime": "^7.18.9", - "@babel/preset-env": "^7.27.2", - "@babel/preset-typescript": "^7.26.0", + "@babel/preset-env": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@babel/runtime": "^7.28.3", "core-js": "^3.45.1", "crypto-js": "^4.2.0", @@ -25,16 +24,15 @@ "iab-adcom": "^1.0.6", "iab-native": "^1.0.0", "iab-openrtb": "^1.0.1", - "karma-safarinative-launcher": "^1.1.0", "klona": "^2.0.6", "live-connect-js": "^7.2.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.16.5", + "@babel/eslint-parser": "^7.28.6", "@babel/plugin-transform-runtime": "^7.27.4", "@babel/register": "^7.28.3", "@chiragrupani/karma-chromium-edge-launcher": "^2.4.1", - "@eslint/compat": "^1.3.1", + "@eslint/compat": "^1.4.1", "@types/google-publisher-tag": "^1.20250210.0", "@wdio/browserstack-service": "^9.19.1", "@wdio/cli": "^9.19.1", @@ -82,6 +80,7 @@ "karma-mocha-reporter": "^2.2.5", "karma-opera-launcher": "^1.0.0", "karma-safari-launcher": "^1.0.0", + "karma-safarinative-launcher": "^1.1.0", "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.4.0", @@ -111,7 +110,7 @@ "videojs-playlist": "^5.2.0", "webdriver": "^9.19.2", "webdriverio": "^9.18.4", - "webpack": "^5.102.1", + "webpack": "^5.103.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.1", "webpack-stream": "^7.0.0", @@ -128,10 +127,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -140,7 +141,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -177,7 +180,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.24.7", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz", + "integrity": "sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA==", "dev": true, "license": "MIT", "dependencies": { @@ -194,13 +199,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -220,10 +225,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -234,15 +241,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -253,11 +262,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -268,19 +279,44 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/helper-define-polyfill-provider/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==", + "license": "MIT" + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -291,36 +327,40 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -340,7 +380,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -348,6 +390,8 @@ }, "node_modules/@babel/helper-remap-async-to-generator": { "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -362,12 +406,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -395,7 +441,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -409,12 +457,14 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -434,12 +484,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -449,11 +499,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -504,11 +556,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -528,10 +582,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -541,10 +597,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -568,11 +626,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -609,12 +668,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", + "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -624,11 +685,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { @@ -652,10 +715,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -665,11 +730,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -679,11 +746,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -693,15 +762,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -710,19 +781,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -732,10 +798,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -745,11 +814,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -772,11 +843,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", + "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -798,11 +871,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -854,10 +945,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -880,10 +973,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -920,11 +1015,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -934,13 +1031,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -991,10 +1090,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1004,10 +1105,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1017,13 +1120,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1047,10 +1153,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1060,10 +1168,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1074,7 +1184,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1087,11 +1199,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1101,12 +1215,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1129,10 +1245,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", + "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1142,11 +1260,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1201,10 +1321,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1254,15 +1376,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", - "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" + "@babel/plugin-syntax-typescript": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1285,11 +1408,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1313,11 +1438,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1327,77 +1454,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.27.2", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.27.1", - "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.27.1", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", - "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.27.1", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "engines": { @@ -1407,6 +1537,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "license": "MIT", @@ -1420,15 +1563,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1561,29 +1705,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", "debug": "^4.3.1" }, "engines": { @@ -1591,13 +1737,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1620,6 +1766,7 @@ }, "node_modules/@colors/colors": { "version": "1.5.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.1.90" @@ -1680,4685 +1827,15269 @@ "node": ">=16" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/compat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.1.tgz", - "integrity": "sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^8.40 || 9" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/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, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": ">=18" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", - "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.15.1", - "levn": "^0.4.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" } }, - "node_modules/@gulp-sourcemaps/identity-map": { - "version": "2.0.1", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^6.4.1", - "normalize-path": "^3.0.0", - "postcss": "^7.0.16", - "source-map": "^0.6.0", - "through2": "^3.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.10" + "node": ">=18" } }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { - "version": "6.4.2", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.4.0" + "node": ">=18" } }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { - "version": "3.0.2", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@gulp-sourcemaps/map-sources": { - "version": "1.0.0", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.10" + "node": ">=18" } }, - "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { - "version": "2.1.1", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/@gulpjs/messages": { - "version": "1.1.0", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=10.13.0" + "node": ">=18" } }, - "node_modules/@gulpjs/to-absolute-glob": { - "version": "4.0.0", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "is-negated-glob": "^1.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=10.13.0" + "node": ">=18" } }, - "node_modules/@hapi/boom": { - "version": "9.1.4", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "9.x.x" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@hapi/cryptiles": { - "version": "5.1.0", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/boom": "9.x.x" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.18.0" + "node": ">=18" } }, - "node_modules/@humanfs/node": { - "version": "0.16.6", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.18.0" + "node": ">=18" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=12.22" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@inquirer/checkbox": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", - "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", + "node_modules/@eslint/compat": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", + "integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" + "@eslint/core": "^0.17.0" }, "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "@types/node": ">=18" + "eslint": "^8.40 || 9" }, "peerDependenciesMeta": { - "@types/node": { + "eslint": { "optional": true } } }, - "node_modules/@inquirer/confirm": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", - "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "node_modules/@eslint/compat/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@inquirer/core": { - "version": "10.1.15", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", - "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@inquirer/editor": { - "version": "4.2.16", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.16.tgz", - "integrity": "sha512-iSzLjT4C6YKp2DU0fr8T7a97FnRRxMO6CushJnW5ktxLNM2iNeuyUuUA5255eOLPORoGYCrVnuDOEBdGkHGkpw==", + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/external-editor": "^1.0.0", - "@inquirer/type": "^3.0.8" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@inquirer/expand": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", - "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.0.tgz", - "integrity": "sha512-5v3YXc5ZMfL6OJqXPrX9csb4l7NlQA2doO1yynUjpUChT9hg4JcuBVP0RbsEJ/3SL/sxWEyFjT2W69ZhtoBWqg==", + "node_modules/@eslint/eslintrc/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, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "chardet": "^2.1.0", - "iconv-lite": "^0.6.3" - }, "engines": { "node": ">=18" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@inquirer/external-editor/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==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@inquirer/figures": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", - "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@inquirer/input": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", - "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8" - }, + "license": "Apache-2.0", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@inquirer/number": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", - "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", + "node_modules/@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8" + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@inquirer/password": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", - "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", + "node_modules/@gulp-sourcemaps/identity-map": { + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8", - "ansi-escapes": "^4.3.2" + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/@inquirer/prompts": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.0.tgz", - "integrity": "sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==", + "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { + "version": "6.4.2", "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.2.0", - "@inquirer/confirm": "^5.1.14", - "@inquirer/editor": "^4.2.15", - "@inquirer/expand": "^4.0.17", - "@inquirer/input": "^4.2.1", - "@inquirer/number": "^3.0.17", - "@inquirer/password": "^4.0.17", - "@inquirer/rawlist": "^4.1.5", - "@inquirer/search": "^3.1.0", - "@inquirer/select": "^4.3.1" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=0.4.0" } }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", - "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", + "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { + "version": "3.0.2", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, - "node_modules/@inquirer/search": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", - "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", + "node_modules/@gulp-sourcemaps/map-sources": { + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" + "normalize-path": "^2.0.1", + "through2": "^2.0.3" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/@inquirer/select": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", - "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", + "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { + "version": "2.1.1", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" + "remove-trailing-separator": "^1.0.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/@inquirer/type": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", - "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { + "version": "2.0.5", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "node_modules/@gulpjs/messages": { + "version": "1.1.0", "dev": true, "license": "MIT", "engines": { - "node": "20 || >=22" + "node": ">=10.13.0" } }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "@isaacs/balanced-match": "^4.0.1" + "is-negated-glob": "^1.0.0" }, "engines": { - "node": "20 || >=22" + "node": ">=10.13.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/@hapi/boom": { + "version": "9.1.4", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" + "@hapi/hoek": "9.x.x" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", + "node_modules/@hapi/cryptiles": { + "version": "5.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "9.x.x" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=12.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/@hapi/hoek": { + "version": "9.3.0", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/@humanfs/core": { + "version": "0.19.1", "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.18.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/@humanfs/node": { + "version": "0.16.6", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.18.0" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "license": "Apache-2.0", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" + "node": ">=12.22" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/get-type": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", - "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "node_modules/@inquirer/checkbox": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", + "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/@inquirer/core": { + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=14" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", + "node_modules/@inquirer/editor": { + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.16.tgz", + "integrity": "sha512-iSzLjT4C6YKp2DU0fr8T7a97FnRRxMO6CushJnW5ktxLNM2iNeuyUuUA5255eOLPORoGYCrVnuDOEBdGkHGkpw==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@inquirer/core": "^10.1.15", + "@inquirer/external-editor": "^1.0.0", + "@inquirer/type": "^3.0.8" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", + "node_modules/@inquirer/expand": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", + "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=7.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", + "node_modules/@inquirer/external-editor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.0.tgz", + "integrity": "sha512-5v3YXc5ZMfL6OJqXPrX9csb4l7NlQA2doO1yynUjpUChT9hg4JcuBVP0RbsEJ/3SL/sxWEyFjT2W69ZhtoBWqg==", "dev": true, "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.6.3" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", + "node_modules/@inquirer/external-editor/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, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "node": ">=0.10.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@inquirer/input": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", + "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@inquirer/number": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", + "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", + "node_modules/@inquirer/password": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", + "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", "dev": true, "license": "MIT", "dependencies": { - "eslint-scope": "5.1.1" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", + "node_modules/@inquirer/prompts": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.0.tgz", + "integrity": "sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@inquirer/checkbox": "^4.2.0", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.15", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" }, "engines": { - "node": ">= 8" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", + "node_modules/@inquirer/rawlist": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", + "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, "engines": { - "node": ">= 8" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", + "node_modules/@inquirer/search": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", + "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">= 8" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@percy/appium-app": { - "version": "2.1.0", + "node_modules/@inquirer/select": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", + "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", "dev": true, "license": "MIT", "dependencies": { - "@percy/sdk-utils": "^1.30.9", - "tmp": "^0.2.3" + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=14" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@percy/sdk-utils": { - "version": "1.31.0", + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@percy/selenium-webdriver": { - "version": "2.2.3", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@percy/sdk-utils": "^1.30.9", - "node-request-interceptor": "^0.6.3" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "node": ">=12" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.25", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", "dev": true, "license": "MIT" }, - "node_modules/@promptbook/utils": { - "version": "0.50.0-10", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://buymeacoffee.com/hejny" - }, - { - "type": "github", - "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing" - } - ], - "license": "CC-BY-4.0", + "license": "MIT", "dependencies": { - "moment": "2.30.1", - "prettier": "2.8.1", - "spacetrim": "0.11.25" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", - "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.4.1", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.2", - "tar-fs": "^3.0.8", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@puppeteer/browsers/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", "dev": true, + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@puppeteer/browsers/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", + "peer": true, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.2", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", "dev": true, "license": "MIT", "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" + "jest-get-type": "^29.6.3" }, "engines": { - "node": ">=12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "dev": true, - "license": "MIT" - }, - "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==", + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "peer": true, "dependencies": { - "type-detect": "4.0.8" + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", + "node_modules/@jest/schemas": { + "version": "29.6.3", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.2", + "node_modules/@jest/types": { + "version": "29.6.3", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "license": "MIT" - }, - "node_modules/@stylistic/eslint-plugin": { - "version": "2.11.0", + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.13.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, - "peerDependencies": { - "eslint": ">=8.40.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.2.0", + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@stylistic/eslint-plugin/node_modules/estraverse": { - "version": "5.3.0", + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT" + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { - "version": "4.0.2", + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=8" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "dev": true, - "license": "MIT" + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", - "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", "license": "MIT" }, - "node_modules/@types/cors": { - "version": "2.8.17", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { - "@types/node": "*" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "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==", + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "eslint-scope": "5.1.1" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/expect": { - "version": "1.20.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/gitconfiglocal": { - "version": "2.0.3", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", "dev": true, - "license": "MIT" - }, - "node_modules/@types/google-publisher-tag": { - "version": "1.20250428.0", - "resolved": "https://registry.npmjs.org/@types/google-publisher-tag/-/google-publisher-tag-1.20250428.0.tgz", - "integrity": "sha512-W+aTMsM4e8PE/TkH/RkMbmmwEFg2si9eUugS5/lt88wkEClqcALi+3WLXW39Xgzu89+3igi/RNIpPLKdt6W7Dg==", - "dev": true + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 8" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" + "engines": { + "node": ">=12.4.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", + "node_modules/@open-draft/until": { + "version": "1.0.3", "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.6", + "node_modules/@percy/appium-app": { + "version": "2.1.0", "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.14.2", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "dev": true, - "license": "MIT" + "@percy/sdk-utils": "^1.30.9", + "tmp": "^0.2.3" + }, + "engines": { + "node": ">=14" + } }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", + "node_modules/@percy/sdk-utils": { + "version": "1.31.0", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=14" + } }, - "node_modules/@types/vinyl": { - "version": "2.0.12", + "node_modules/@percy/selenium-webdriver": { + "version": "2.2.3", "dev": true, "license": "MIT", "dependencies": { - "@types/expect": "^1.20.4", - "@types/node": "*" + "@percy/sdk-utils": "^1.30.9", + "node-request-interceptor": "^0.6.3" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@types/which": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "optional": true, + "engines": { + "node": ">=14" } }, - "node_modules/@types/yargs": { - "version": "17.0.33", + "node_modules/@pkgr/core": { + "version": "0.1.1", "dev": true, "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", + "node_modules/@polka/url": { + "version": "1.0.0-next.25", "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", + "node_modules/@promptbook/utils": { + "version": "0.50.0-10", "dev": true, - "license": "MIT", - "optional": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "CC-BY-4.0", "dependencies": { - "@types/node": "*" + "moment": "2.30.1", + "prettier": "2.8.1", + "spacetrim": "0.11.25" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", - "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", + "node_modules/@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/type-utils": "8.39.0", - "@typescript-eslint/utils": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "browsers": "lib/cjs/main-cli.js" }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.39.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=18" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", - "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", - "debug": "^4.3.4" + "ms": "^2.1.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6.0" }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", - "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.0", - "@typescript-eslint/types": "^8.39.0", - "debug": "^4.3.4" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", - "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.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": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=12" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", - "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "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, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", - "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/utils": "8.39.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "type-detect": "4.0.8" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", - "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", - "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@typescript-eslint/project-service": "8.39.0", - "@typescript-eslint/tsconfig-utils": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/@typescript-eslint/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", + "node_modules/@stylistic/eslint-plugin": { + "version": "2.11.0", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0" + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": ">=8.40.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", - "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.39.0", - "eslint-visitor-keys": "^4.2.1" - }, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/@stylistic/eslint-plugin/node_modules/estraverse": { + "version": "5.3.0", "dev": true, - "license": "Apache-2.0", + "license": "BSD-2-Clause", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=4.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ] + "dependencies": { + "tslib": "^2.4.0" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], + "node_modules/@types/cookie": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.17", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], + "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, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], + "node_modules/@types/expect": { + "version": "1.20.4", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], + "node_modules/@types/gitconfiglocal": { + "version": "2.0.3", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@types/google-publisher-tag": { + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@types/google-publisher-tag/-/google-publisher-tag-1.20250428.0.tgz", + "integrity": "sha512-W+aTMsM4e8PE/TkH/RkMbmmwEFg2si9eUugS5/lt88wkEClqcALi+3WLXW39Xgzu89+3igi/RNIpPLKdt6W7Dg==", + "dev": true }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/istanbul-lib-report": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@types/json-schema": { + "version": "7.0.15", + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], + "node_modules/@types/json5": { + "version": "0.0.29", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], + "node_modules/@types/mocha": { + "version": "10.0.6", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@types/node": { + "version": "20.14.2", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" + "undici-types": "~5.26.4" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/@types/stack-utils": { + "version": "2.0.3", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, - "node_modules/@videojs/http-streaming": { - "version": "2.16.3", + "node_modules/@types/triple-beam": { + "version": "1.3.5", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "3.0.5", - "aes-decrypter": "3.1.3", - "global": "^4.4.0", - "m3u8-parser": "4.8.0", - "mpd-parser": "^0.22.1", - "mux.js": "6.0.1", - "video.js": "^6 || ^7" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "video.js": "^6 || ^7" - } + "license": "MIT" }, - "node_modules/@videojs/vhs-utils": { - "version": "3.0.5", + "node_modules/@types/vinyl": { + "version": "2.0.12", "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0", - "url-toolkit": "^2.2.1" - }, - "engines": { - "node": ">=8", - "npm": ">=5" + "@types/expect": "^1.20.4", + "@types/node": "*" } }, - "node_modules/@videojs/xhr": { - "version": "2.6.0", - "dev": true, + "node_modules/@types/which": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.5.5", - "global": "~4.4.0", - "is-function": "^1.0.1" + "@types/node": "*" } }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.9", + "node_modules/@types/yargs": { + "version": "17.0.33", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@types/yargs-parser": "*" } }, - "node_modules/@vitest/snapshot": { - "version": "2.1.9", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@types/node": "*" } }, - "node_modules/@wdio/browserstack-service": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.19.1.tgz", - "integrity": "sha512-9goHyn4PckZXp1GlYgTGslCFd+2BJPcrRjzWD8ziXJ7WWIC8/94+Y3Sp3HTnlZayrdSIjTXtHKZODP1qugVcWg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", + "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", "dev": true, "license": "MIT", "dependencies": { - "@browserstack/ai-sdk-node": "1.5.17", - "@percy/appium-app": "^2.0.9", - "@percy/selenium-webdriver": "^2.2.2", - "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "9.18.0", - "@wdio/reporter": "9.19.1", - "@wdio/types": "9.19.1", - "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", - "undici": "^6.21.3", - "uuid": "^11.1.0", - "webdriverio": "9.19.1", - "winston-transport": "^4.5.0", - "yauzl": "^3.0.0" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/type-utils": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": ">=18.20.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + "@typescript-eslint/parser": "^8.39.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/logger": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", - "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "safe-regex2": "^5.0.0", - "strip-ansi": "^7.1.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">= 4" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/reporter": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.19.1.tgz", - "integrity": "sha512-nv5TZg+rUUlC3NGNDP2DGzpd2grU/3D27M9MsPV37TjGLccEVZYlbu2BnK3Y9mSqXWt7CZuFC4GXBF+5vQG6gw==", + "node_modules/@typescript-eslint/parser": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", + "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0", - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "diff": "^8.0.2", - "object-inspect": "^1.12.0" + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=18.20.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/types": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", - "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0" + "@typescript-eslint/tsconfig-utils": "^8.39.0", + "@typescript-eslint/types": "^8.39.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=18.20.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/chalk": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", - "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@wdio/browserstack-service/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/cli": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.19.1.tgz", - "integrity": "sha512-1JDvutIp1mYk2f3KaBdcHAMw9UQlAMFnXbB4byuOMgik75HIgF+mrsasHj8wzfJTm9BbLwQ2h/6yGLHPTXvc0g==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", + "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/snapshot": "^2.1.1", - "@wdio/config": "9.19.1", - "@wdio/globals": "9.17.0", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "async-exit-hook": "^2.0.1", - "chalk": "^5.4.1", - "chokidar": "^4.0.0", - "create-wdio": "9.18.2", - "dotenv": "^17.2.0", - "import-meta-resolve": "^4.0.0", - "lodash.flattendeep": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.union": "^4.6.0", - "read-pkg-up": "^10.0.0", - "tsx": "^4.7.2", - "webdriverio": "9.19.1", - "yargs": "^17.7.2" - }, - "bin": { - "wdio": "bin/wdio.js" + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": ">=18.20.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/cli/node_modules/@jest/expect-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", - "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "node_modules/@typescript-eslint/types": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.0.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@wdio/cli/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@sinclair/typebox": "^0.34.0" + "@typescript-eslint/project-service": "8.39.0", + "@typescript-eslint/tsconfig-utils": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/cli/node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@wdio/cli/node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "18 || 20 || >=22" } }, - "node_modules/@wdio/cli/node_modules/@jest/types/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/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "license": "MIT", - "peer": true + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/@wdio/cli/node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "tinyrainbow": "^2.0.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@wdio/cli/node_modules/@wdio/config": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", - "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", "dev": true, "license": "MIT", "dependencies": { - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "deepmerge-ts": "^7.0.3", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" + "@typescript-eslint/types": "8.39.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=18.20.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@wdio/cli/node_modules/@wdio/globals": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.17.0.tgz", - "integrity": "sha512-i38o7wlipLllNrk2hzdDfAmk6nrqm3lR2MtAgWgtHbwznZAKkB84KpkNFfmUXw5Kg3iP1zKlSjwZpKqenuLc+Q==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18.20.0" - }, - "peerDependencies": { - "expect-webdriverio": "^5.3.4", - "webdriverio": "^9.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "expect-webdriverio": { - "optional": false - }, - "webdriverio": { - "optional": false - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@wdio/cli/node_modules/@wdio/logger": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", - "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "safe-regex2": "^5.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18.20.0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@wdio/cli/node_modules/@wdio/protocols": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", - "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@wdio/cli/node_modules/@wdio/types": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", - "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": ">=18.20.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@wdio/cli/node_modules/@wdio/utils": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", - "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.2", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "mitt": "^3.0.1", - "safaridriver": "^1.0.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@videojs/http-streaming": { + "version": "2.16.3", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "3.0.5", + "aes-decrypter": "3.1.3", + "global": "^4.4.0", + "m3u8-parser": "4.8.0", + "mpd-parser": "^0.22.1", + "mux.js": "6.0.1", + "video.js": "^6 || ^7" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "video.js": "^6 || ^7" + } + }, + "node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/xhr": { + "version": "2.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/browserstack-service": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.19.1.tgz", + "integrity": "sha512-9goHyn4PckZXp1GlYgTGslCFd+2BJPcrRjzWD8ziXJ7WWIC8/94+Y3Sp3HTnlZayrdSIjTXtHKZODP1qugVcWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@browserstack/ai-sdk-node": "1.5.17", + "@percy/appium-app": "^2.0.9", + "@percy/selenium-webdriver": "^2.2.2", + "@types/gitconfiglocal": "^2.0.1", + "@wdio/logger": "9.18.0", + "@wdio/reporter": "9.19.1", + "@wdio/types": "9.19.1", + "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", + "undici": "^6.21.3", + "uuid": "^11.1.0", + "webdriverio": "9.19.1", + "winston-transport": "^4.5.0", + "yauzl": "^3.0.0" + }, + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@wdio/reporter": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.19.1.tgz", + "integrity": "sha512-nv5TZg+rUUlC3NGNDP2DGzpd2grU/3D27M9MsPV37TjGLccEVZYlbu2BnK3Y9mSqXWt7CZuFC4GXBF+5vQG6gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "diff": "^8.0.2", + "object-inspect": "^1.12.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@wdio/types": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@wdio/cli": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.19.1.tgz", + "integrity": "sha512-1JDvutIp1mYk2f3KaBdcHAMw9UQlAMFnXbB4byuOMgik75HIgF+mrsasHj8wzfJTm9BbLwQ2h/6yGLHPTXvc0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/snapshot": "^2.1.1", + "@wdio/config": "9.19.1", + "@wdio/globals": "9.17.0", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "async-exit-hook": "^2.0.1", + "chalk": "^5.4.1", + "chokidar": "^4.0.0", + "create-wdio": "9.18.2", + "dotenv": "^17.2.0", + "import-meta-resolve": "^4.0.0", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "read-pkg-up": "^10.0.0", + "tsx": "^4.7.2", + "webdriverio": "9.19.1", + "yargs": "^17.7.2" + }, + "bin": { + "wdio": "bin/wdio.js" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/cli/node_modules/@jest/expect-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/get-type": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/@jest/types/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, + "license": "MIT", + "peer": 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/@wdio/cli/node_modules/@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@wdio/cli/node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/cli/node_modules/@wdio/config": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", + "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/cli/node_modules/@wdio/globals": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.17.0.tgz", + "integrity": "sha512-i38o7wlipLllNrk2hzdDfAmk6nrqm3lR2MtAgWgtHbwznZAKkB84KpkNFfmUXw5Kg3iP1zKlSjwZpKqenuLc+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "expect-webdriverio": "^5.3.4", + "webdriverio": "^9.0.0" + }, + "peerDependenciesMeta": { + "expect-webdriverio": { + "optional": false + }, + "webdriverio": { + "optional": false + } + } + }, + "node_modules/@wdio/cli/node_modules/@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/cli/node_modules/@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wdio/cli/node_modules/@wdio/types": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/cli/node_modules/@wdio/utils": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", + "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/cli/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/chokidar": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@wdio/cli/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@wdio/cli/node_modules/expect": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/expect-webdriverio": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.4.1.tgz", + "integrity": "sha512-jH4qhahRNGPWbCCVcCpLDl/kvFJ1eOzVnrd1K/sG1RhKr6bZsgZQUiOE3bafVqSOfKP+ay8bM/VagP4+XsO9Xw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/snapshot": "^3.2.4", + "expect": "^30.0.0", + "jest-matcher-utils": "^30.0.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">=18 || >=20 || >=22" + }, + "peerDependencies": { + "@wdio/globals": "^9.0.0", + "@wdio/logger": "^9.0.0", + "webdriverio": "^9.0.0" + }, + "peerDependenciesMeta": { + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false + } + } + }, + "node_modules/@wdio/cli/node_modules/expect-webdriverio/node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/cli/node_modules/jest-diff": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/jest-diff/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, + "license": "MIT", + "peer": 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/@wdio/cli/node_modules/jest-matcher-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/jest-matcher-utils/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, + "license": "MIT", + "peer": 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/@wdio/cli/node_modules/jest-message-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/jest-message-util/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, + "license": "MIT", + "peer": 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/@wdio/cli/node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/jest-util/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, + "license": "MIT", + "peer": 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/@wdio/cli/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@wdio/cli/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@wdio/cli/node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/readdirp": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@wdio/cli/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/cli/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "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_modules/@wdio/concise-reporter": { + "version": "8.38.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", + "chalk": "^5.0.1", + "pretty-ms": "^7.0.1" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/concise-reporter/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/config": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/config/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/config/node_modules/@wdio/types": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/config/node_modules/@wdio/utils": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/config/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/dot-reporter": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/reporter": "9.15.0", + "@wdio/types": "9.15.0", + "chalk": "^5.0.1" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/dot-reporter/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/dot-reporter/node_modules/@wdio/reporter": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "diff": "^7.0.0", + "object-inspect": "^1.12.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/dot-reporter/node_modules/@wdio/types": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/dot-reporter/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/dot-reporter/node_modules/diff": { + "version": "7.0.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@wdio/globals": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20.0" + }, + "optionalDependencies": { + "expect-webdriverio": "^5.1.0", + "webdriverio": "9.15.0" + } + }, + "node_modules/@wdio/globals/node_modules/@vitest/pretty-format": { + "version": "3.2.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/globals/node_modules/@vitest/snapshot": { + "version": "3.2.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@vitest/pretty-format": "3.2.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/globals/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/globals/node_modules/@wdio/types": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.15.0.tgz", + "integrity": "sha512-hR0Dm9TsrjtgOLWOjUMYTOB1hWIlnDzFgZt7XGOzI9Ig8Qa+TDfZSFaZukGxqLIZS/eGhxpnunSHaTAXwJIxYA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/globals/node_modules/@wdio/utils": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.15.0.tgz", + "integrity": "sha512-XuT1PE1nh4wwJfQW6IN4UT6+iv0+Yf4zhgMh5et04OX6tfrIXkWdx2SDimghDtRukp9i85DvIGWjdPEoQFQdaA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/globals/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "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/expect-webdriverio": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@vitest/snapshot": "^3.2.1", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">=18 || >=20 || >=22" + }, + "peerDependencies": { + "@wdio/globals": "^9.0.0", + "@wdio/logger": "^9.0.0", + "webdriverio": "^9.0.0" + }, + "peerDependenciesMeta": { + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false + } + } + }, + "node_modules/@wdio/globals/node_modules/htmlfy": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", + "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@wdio/globals/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@wdio/globals/node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/globals/node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.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, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/globals/node_modules/webdriver": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.15.0.tgz", + "integrity": "sha512-JCW5xvhZtL6kjbckdePgVYMOlvWbh22F1VFkIf9pw3prwXI2EHED5Eq/nfDnNfHiqr0AfFKWmIDPziSafrVv4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "undici": "^6.20.1", + "ws": "^8.8.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/globals/node_modules/webdriverio": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.15.0.tgz", + "integrity": "sha512-910g6ktwXdAKGyhgCPGw9BzIKOEBBYMFN1bLwC3bW/3mFlxGHO/n70c7Sg9hrsu9VWTzv6m+1Clf27B9uz4a/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/repl": "9.4.4", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "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.6.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "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.15.0" + }, + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "puppeteer-core": ">=22.x || <=24.x" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } + } + }, + "node_modules/@wdio/local-runner": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.15.0", + "@wdio/repl": "9.4.4", + "@wdio/runner": "9.15.0", + "@wdio/types": "9.15.0", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/local-runner/node_modules/@wdio/types": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/local-runner/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/logger": { + "version": "8.38.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/mocha-framework": { + "version": "9.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mocha": "^10.0.6", + "@types/node": "^20.11.28", + "@wdio/logger": "9.4.4", + "@wdio/types": "9.12.6", + "@wdio/utils": "9.12.6", + "mocha": "^10.3.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/mocha-framework/node_modules/@wdio/logger": { + "version": "9.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/mocha-framework/node_modules/@wdio/types": { + "version": "9.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/mocha-framework/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/protocols": { + "version": "9.15.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@wdio/repl": { + "version": "9.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/reporter": { + "version": "8.38.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@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": "^16.13 || >=18" + } + }, + "node_modules/@wdio/runner": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.11.28", + "@wdio/config": "9.15.0", + "@wdio/dot-reporter": "9.15.0", + "@wdio/globals": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.1.0", + "webdriver": "9.15.0", + "webdriverio": "9.15.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/runner/node_modules/@vitest/pretty-format": { + "version": "3.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/runner/node_modules/@vitest/snapshot": { + "version": "3.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/runner/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/runner/node_modules/@wdio/types": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/runner/node_modules/@wdio/utils": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/runner/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/runner/node_modules/expect-webdriverio": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/snapshot": "^3.2.1", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">=18 || >=20 || >=22" + }, + "peerDependencies": { + "@wdio/globals": "^9.0.0", + "@wdio/logger": "^9.0.0", + "webdriverio": "^9.0.0" + }, + "peerDependenciesMeta": { + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false + } + } + }, + "node_modules/@wdio/runner/node_modules/htmlfy": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", + "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wdio/runner/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/runner/node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.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, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/runner/node_modules/webdriver": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.15.0.tgz", + "integrity": "sha512-JCW5xvhZtL6kjbckdePgVYMOlvWbh22F1VFkIf9pw3prwXI2EHED5Eq/nfDnNfHiqr0AfFKWmIDPziSafrVv4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "undici": "^6.20.1", + "ws": "^8.8.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.15.0.tgz", + "integrity": "sha512-910g6ktwXdAKGyhgCPGw9BzIKOEBBYMFN1bLwC3bW/3mFlxGHO/n70c7Sg9hrsu9VWTzv6m+1Clf27B9uz4a/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/repl": "9.4.4", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "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.6.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "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.15.0" + }, + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "puppeteer-core": ">=22.x || <=24.x" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } + } + }, + "node_modules/@wdio/spec-reporter": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.43.0.tgz", + "integrity": "sha512-Qy5LsGrGHbXJdj2PveNV7CD5g1XpLPvd7wH8bAd9OtuZRTPknyZqYSvB8TlZIV/SfiNlWEJ/05mI/FcixNJ6Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/reporter": "8.43.0", + "@wdio/types": "8.41.0", + "chalk": "^5.1.2", + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@types/node": { + "version": "22.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", + "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.43.0.tgz", + "integrity": "sha512-0ph8SabdMrgDmuLUEwA7yvkrvlCw4/EXttb3CGucjSkuiSbZNLhdTMXpyPoewh2soa253fIpnx79HztOsOzn5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^22.2.0", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.41.0", + "diff": "^7.0.0", + "object-inspect": "^1.12.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.41.0.tgz", + "integrity": "sha512-t4NaNTvJZci3Xv/yUZPH4eTL0hxrVTf5wdwNnYIBrzMnlRDbNefjQ0P7FM7ZjQCLaH92AEH6t/XanUId7Webug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^22.2.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wdio/types": { + "version": "8.38.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/utils": { + "version": "9.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.4.4", + "@wdio/types": "9.12.6", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", "split2": "^4.2.0", "wait-port": "^1.1.0" }, "engines": { - "node": ">=18.20.0" + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/utils/node_modules/@wdio/logger": { + "version": "9.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/utils/node_modules/@wdio/types": { + "version": "9.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/utils/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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, + "license": "Apache-2.0" + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.60", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, + "node_modules/abbrev": { + "version": "1.0.9", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-decrypter": { + "version": "3.1.3", + "dev": true, + "license": "Apache-2.0", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "dev": true, + "license": "BSD-3-Clause OR MIT", + "optional": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-cyan": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-red": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "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/archiver-utils": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.6", + "dev": true, + "license": "MIT" + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/buffer-crc32": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "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/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/arr-diff": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-differ": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", + "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "dev": true, + "license": "MIT", + "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-slice": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assert": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "1.5.2", + "dev": true, + "license": "MIT" + }, + "node_modules/async-done": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-settle": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bach": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.2", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "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" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binaryextensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "6.0.3", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "dev": true, + "license": "MIT", + "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", + "license": "MIT" + }, + "node_modules/body": { + "version": "5.1.0", + "dev": true, + "dependencies": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body/node_modules/bytes": { + "version": "1.0.0", + "dev": true + }, + "node_modules/body/node_modules/raw-body": { + "version": "1.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/body/node_modules/string_decoder": { + "version": "0.10.31", + "dev": true, + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack": { + "version": "1.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "https-proxy-agent": "^2.2.1" + } + }, + "node_modules/browserstack-local": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.11.tgz", + "integrity": "sha512-RNq0yrezPq7BXXxl/cvsbORfswUQi744po6ECkTEC2RkqNbdPyzewdy4VR9k4QHSzPHTkZx8PeH08veRtfFI8A==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "tree-kill": "^1.2.2" + } + }, + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/bufferstreams": { + "version": "1.0.1", + "dependencies": { + "readable-stream": "^1.0.33" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/bufferstreams/node_modules/isarray": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/bufferstreams/node_modules/readable-stream": { + "version": "1.1.14", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bufferstreams/node_modules/string_decoder": { + "version": "0.10.31", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/can-autoplay": { + "version": "3.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/check-error": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "BSD-2-Clause", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "dev": true, + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "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/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "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/compress-commons/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-livereload": { + "version": "0.6.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/consolidate": { + "version": "0.15.1", + "license": "MIT", + "dependencies": { + "bluebird": "^3.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/continuable-cache": { + "version": "0.3.1", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/copy-props": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", + "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/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/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/cosmiconfig/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/cosmiconfig/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/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "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/crc32-stream/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/create-wdio": { + "version": "9.18.2", + "resolved": "https://registry.npmjs.org/create-wdio/-/create-wdio-9.18.2.tgz", + "integrity": "sha512-atf81YJfyTNAJXsNu3qhpqF4OO43tHGTpr88duAc1Hk4a0uXJAPUYLnYxshOuMnfmeAxlWD+NqGU7orRiXEuJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^14.0.0", + "cross-spawn": "^7.0.3", + "ejs": "^3.1.10", + "execa": "^9.6.0", + "import-meta-resolve": "^4.1.0", + "inquirer": "^12.7.0", + "normalize-package-data": "^7.0.0", + "read-pkg-up": "^10.1.0", + "recursive-readdir": "^2.2.3", + "semver": "^7.6.3", + "type-fest": "^4.41.0", + "yargs": "^17.7.2" + }, + "bin": { + "create-wdio": "bin/wdio.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/create-wdio/node_modules/chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/create-wdio/node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/create-wdio/node_modules/execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/create-wdio/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, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-wdio/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/create-wdio/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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-wdio/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/create-wdio/node_modules/normalize-package-data": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.1.tgz", + "integrity": "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/create-wdio/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, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-wdio/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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-wdio/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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-wdio/node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-wdio/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-wdio/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, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/create-wdio/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, + "license": "MIT", + "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_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/css": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.1", + "dev": true + }, + "node_modules/css-value": { + "version": "0.0.1", + "dev": true + }, + "node_modules/css-what": { + "version": "6.1.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csv-writer": { + "version": "1.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/custom-event": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/d": { + "version": "1.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.6", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug-fabulous": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + } + }, + "node_modules/debug-fabulous/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/escodegen": { + "version": "2.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/degenerator/node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/degenerator/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1464554", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", + "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.2.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "dev": true + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexify": { + "version": "4.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/each-props": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/easy-table": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "optionalDependencies": { + "wcwidth": "^1.0.1" + } + }, + "node_modules/edge-paths": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" + } + }, + "node_modules/edge-paths/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/edge-paths/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/edgedriver": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-6.1.2.tgz", + "integrity": "sha512-UvFqd/IR81iPyWMcxXbUNi+xKWR7JjfoHjfuwjqsj9UHQKn80RpQmS0jf+U25IPi+gKVPcpOSKm0XkqgGMq4zQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "^9.1.3", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "fast-xml-parser": "^5.0.8", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "which": "^5.0.0" + }, + "bin": { + "edgedriver": "bin/edgedriver.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/edgedriver/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/edgedriver/node_modules/agent-base": { + "version": "7.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/edgedriver/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/edgedriver/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error": { + "version": "7.2.1", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.3", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.8.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-chai-friendly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.1.0.tgz", + "integrity": "sha512-+T1rClpDdXkgBAhC16vRQMI5umiWojVqkj9oUTdpma50+uByCZM/oBfxitZiOkjMRlm725mwFfz/RVgyDRvCKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=3.0.0" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import-x": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.35.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.0.1", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "eslint-import-resolver-node": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint-plugin-import-x/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint-plugin-import-x/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x/node_modules/minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-import-x/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import-x/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "50.6.6", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.49.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.6", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/eslint-plugin-n": { + "version": "17.21.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.3.tgz", + "integrity": "sha512-MtxYjDZhMQgsWRm/4xYLL0i2EhusWT7itDxlJ80l1NND2AL2Vi5Mvneqv/ikG9+zpran0VsVRXTEHrpLmUZRNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.5.0", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "globrex": "^0.1.2", + "ignore": "^5.3.2", + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "7.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "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": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "2.7.3", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "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.6", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/ext": { + "version": "1.7.0", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/kind-of": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip/node_modules/yauzl": { + "version": "2.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/faker": { + "version": "5.5.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fancy-log": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-support": "^1.1.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-blob/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flagged-respawn": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-stream": { + "version": "0.0.4", + "dev": true, + "license": "BSD" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-node": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fs-readfile-promise": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fun-hooks": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "typescript-tuple": "^2.2.1" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/geckodriver": { + "version": "5.0.0", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "^9.1.3", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.6", + "which": "^5.0.0" + }, + "bin": { + "geckodriver": "bin/geckodriver.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/geckodriver/node_modules/@wdio/logger": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/geckodriver/node_modules/agent-base": { + "version": "7.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/geckodriver/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/geckodriver/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-port": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/git-repo-info": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/gitconfiglocal": { + "version": "2.1.0", + "dev": true, + "license": "BSD", + "dependencies": { + "ini": "^1.3.2" + } + }, + "node_modules/gitconfiglocal/node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "8.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob-watcher": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/glogg": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "sparkles": "^2.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.1.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.2" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-babel": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", + "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", + "dependencies": { + "plugin-error": "^1.0.1", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/gulp-babel/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-babel/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-babel/node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-babel/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-babel/node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-babel/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-babel/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-clean": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2", + "rimraf": "^2.6.2", + "through2": "^2.0.3", + "vinyl": "^2.1.0" + }, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/gulp-clean/node_modules/fancy-log": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-clean/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "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/gulp-clean/node_modules/plugin-error": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clean/node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/gulp-clean/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-cli": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.1", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/gulp-cli/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "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/gulp-cli/node_modules/cliui": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/gulp-cli/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/gulp-cli/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-cli/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-cli/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gulp-cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/gulp-cli/node_modules/yargs": { + "version": "16.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-cli/node_modules/yargs-parser": { + "version": "20.2.9", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-concat": { + "version": "2.6.1", + "dev": true, + "license": "MIT", + "dependencies": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-concat/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-connect": { + "version": "5.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^2.0.5", + "connect": "^3.6.6", + "connect-livereload": "^0.6.0", + "fancy-log": "^1.3.2", + "map-stream": "^0.0.7", + "send": "^0.16.2", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2", + "tiny-lr": "^1.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-connect/node_modules/ansi-colors": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-connect/node_modules/fancy-log": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-filter": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-9.0.1.tgz", + "integrity": "sha512-knVYL8h9bfYIeft3VokVTkuaWJkQJMrFCS3yVjZQC6BGg+1dZFoeUY++B9D2X4eFpeNTx9StWK0qnDby3NO3PA==", + "dev": true, + "dependencies": { + "multimatch": "^7.0.0", + "plugin-error": "^2.0.1", + "slash": "^5.1.0", + "streamfilter": "^3.0.0", + "to-absolute-glob": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-if": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" + } + }, + "node_modules/gulp-if/node_modules/through2": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp-js-escape": { + "version": "1.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "^0.6.3" + } + }, + "node_modules/gulp-js-escape/node_modules/isarray": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-js-escape/node_modules/readable-stream": { + "version": "1.0.34", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-js-escape/node_modules/string_decoder": { + "version": "0.10.31", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-js-escape/node_modules/through2": { + "version": "0.6.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/gulp-match": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.3" + } + }, + "node_modules/gulp-rename": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.1.0.tgz", + "integrity": "sha512-dGuzuH8jQGqCMqC544IEPhs5+O2l+IkdoSZsgd4kY97M1CxQeI3qrmweQBIrxLBbjbe/8uEWK8HHcNBc3OCy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-replace": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-sourcemaps": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gulp-sourcemaps/node_modules/acorn": { + "version": "6.4.2", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-sourcemaps/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-tap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-tap/-/gulp-tap-2.0.0.tgz", + "integrity": "sha512-U5/v1bTozx672QHzrvzPe6fPl2io7Wqyrx2y30AG53eMU/idH4BrY/b2yikOkdyhjDqGgPoMUMnpBg9e9LK8Nw==", + "dev": true, + "dependencies": { + "through2": "^3.0.1" + } + }, + "node_modules/gulp-tap/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp-wrap": { + "version": "0.15.0", + "dependencies": { + "consolidate": "^0.15.1", + "es6-promise": "^4.2.6", + "fs-readfile-promise": "^3.0.1", + "js-yaml": "^3.13.0", + "lodash": "^4.17.11", + "node.extend": "2.0.2", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tryit": "^1.0.1", + "vinyl-bufferstream": "^1.0.1" + }, + "engines": { + "node": ">=6.14", + "npm": ">=1.4.3" + } + }, + "node_modules/gulp-wrap/node_modules/ansi-colors": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/arr-diff": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/arr-union": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/extend-shallow": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/plugin-error": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-wrap/node_modules/through2": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulplog": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "glogg": "^2.2.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/headers-utils": { + "version": "1.2.5", + "dev": true, + "license": "MIT" + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.2.2", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlfy": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.8.1.tgz", + "integrity": "sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "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", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iab-adcom": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/iab-adcom/-/iab-adcom-1.0.6.tgz", + "integrity": "sha512-XAJdidfrFgZNKmHqcXD3Zhqik2rdSmOs+PGgeVfPWgthxvzNBQxkZnKkW3QAau6mrLjtJc8yOQC6awcEv7gryA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/iab-native": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iab-native/-/iab-native-1.0.0.tgz", + "integrity": "sha512-AxGYpKGRcyG5pbEAqj+ssxNwZAfxC0pRwyKc0MYoKjm0UeOoUNCWrZV0HGimcQii6ebe6MRqBQEeENyHM4qTdQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/iab-openrtb": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iab-openrtb/-/iab-openrtb-1.0.1.tgz", + "integrity": "sha512-egawJx6+pMh/6uA/hak1y+R2+XCSH2jxteSkWlY98/XdQQftaMUMllUFNMKrHwq9lgCI70Me06g4JCCnV6E62g==", + "dependencies": { + "iab-adcom": "1.0.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "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" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/individual": { + "version": "2.0.0", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.0.tgz", + "integrity": "sha512-LlFVmvWVCun7uEgPB3vups9NzBrjJn48kRNtFGw3xU1H5UXExTEz/oF1JGLaB0fvlkUB+W6JfgLcSEaSdH7RPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/prompts": "^7.8.0", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "mute-stream": "^2.0.0", + "run-async": "^4.0.5", + "rxjs": "^7.8.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is": { + "version": "3.3.0", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable/node_modules/is-plain-object": { + "version": "2.0.4", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-running": { + "version": "2.1.0", + "dev": true, + "license": "BSD" + }, + "node_modules/is-set": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/isobject": { + "version": "3.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul": { + "version": "0.4.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.6.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul/node_modules/glob": { + "version": "5.0.15", + "dev": true, + "license": "ISC", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul/node_modules/has-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul/node_modules/resolve": { + "version": "1.1.7", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul/node_modules/supports-color": { + "version": "3.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/istanbul/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/istextorbinary": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/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, + "license": "MIT" + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jest-mock/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, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-mock/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, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-mock/node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock/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, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-mock/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, + "license": "MIT", + "peer": true + }, + "node_modules/jest-mock/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, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock/node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-mock/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, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@wdio/cli/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=8" } }, - "node_modules/@wdio/cli/node_modules/chalk": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", - "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": ">= 10.13.0" } }, - "node_modules/@wdio/cli/node_modules/chokidar": { - "version": "4.0.3", + "node_modules/jest-worker/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">=10" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", - "peer": true, "engines": { - "node": ">=8" + "node": ">=12.0.0" } }, - "node_modules/@wdio/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jsesc": { + "version": "3.1.0", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=6" } }, - "node_modules/@wdio/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/jsonfile": { + "version": "6.1.0", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } }, - "node_modules/@wdio/cli/node_modules/expect": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", - "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@jest/expect-utils": "30.0.5", - "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.5", - "jest-message-util": "30.0.5", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 10" } }, - "node_modules/@wdio/cli/node_modules/expect-webdriverio": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.4.1.tgz", - "integrity": "sha512-jH4qhahRNGPWbCCVcCpLDl/kvFJ1eOzVnrd1K/sG1RhKr6bZsgZQUiOE3bafVqSOfKP+ay8bM/VagP4+XsO9Xw==", + "node_modules/karma-babel-preprocessor": { + "version": "8.0.2", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/snapshot": "^3.2.4", - "expect": "^30.0.0", - "jest-matcher-utils": "^30.0.0", - "lodash.isequal": "^4.5.0" - }, + "license": "ISC", "engines": { - "node": ">=18 || >=20 || >=22" + "node": ">=6" }, "peerDependencies": { - "@wdio/globals": "^9.0.0", - "@wdio/logger": "^9.0.0", - "webdriverio": "^9.0.0" - }, - "peerDependenciesMeta": { - "@wdio/globals": { - "optional": false - }, - "@wdio/logger": { - "optional": false - }, - "webdriverio": { - "optional": false - } + "@babel/core": "7" } }, - "node_modules/@wdio/cli/node_modules/expect-webdriverio/node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "node_modules/karma-browserstack-launcher": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", + "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" }, - "funding": { - "url": "https://opencollective.com/vitest" + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/@wdio/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/karma-chai": { + "version": "0.1.0", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" } }, - "node_modules/@wdio/cli/node_modules/jest-diff": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", - "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "pretty-format": "30.0.5" + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "bin": { + "which": "bin/which" } }, - "node_modules/@wdio/cli/node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/karma-coverage": { + "version": "2.2.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=8" + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/mattlewis92" } }, - "node_modules/@wdio/cli/node_modules/jest-diff/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/karma-coverage-istanbul-reporter/node_modules/glob": { + "version": "7.2.3", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "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": ">=10" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/jest-matcher-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", - "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps": { + "version": "3.0.6", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-3-Clause", "dependencies": { - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "jest-diff": "30.0.5", - "pretty-format": "30.0.5" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6" } }, - "node_modules/@wdio/cli/node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { + "version": "2.0.5", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/make-dir": { + "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "pify": "^4.0.1", + "semver": "^5.6.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6" } }, - "node_modules/@wdio/cli/node_modules/jest-matcher-utils/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/karma-coverage-istanbul-reporter/node_modules/pify": { + "version": "4.0.1", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">=6" + } + }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "bin": { + "rimraf": "bin.js" } }, - "node_modules/@wdio/cli/node_modules/jest-message-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", - "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "node_modules/karma-coverage-istanbul-reporter/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/karma-firefox-launcher": { + "version": "2.1.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.0.5", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "is-wsl": "^2.2.0", + "which": "^3.0.0" } }, - "node_modules/@wdio/cli/node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/karma-firefox-launcher/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/karma-firefox-launcher/node_modules/which": { + "version": "3.0.1", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=8" + "bin": { + "node-which": "bin/which.js" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@wdio/cli/node_modules/jest-message-util/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/karma-mocha": { + "version": "2.0.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "minimist": "^1.2.3" } }, - "node_modules/@wdio/cli/node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "node_modules/karma-mocha-reporter": { + "version": "2.2.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "strip-ansi": "^4.0.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "karma": ">=0.13" } }, - "node_modules/@wdio/cli/node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { + "version": "3.0.1", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=4" } }, - "node_modules/@wdio/cli/node_modules/jest-util/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/karma-mocha-reporter/node_modules/strip-ansi": { + "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-regex": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=4" } }, - "node_modules/@wdio/cli/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@wdio/cli/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/karma-opera-launcher": { + "version": "1.0.0", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/@wdio/cli/node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "node_modules/karma-safari-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", + "integrity": "sha512-qmypLWd6F2qrDJfAETvXDfxHvKDk+nyIjpH9xIeI3/hENr0U3nuqkxaftq73PfXZ4aOuOChA6SnLW4m4AxfRjQ==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/@wdio/cli/node_modules/readdirp": { - "version": "4.1.2", + "node_modules/karma-safarinative-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-safarinative-launcher/-/karma-safarinative-launcher-1.1.0.tgz", + "integrity": "sha512-vdMjdQDHkSUbOZc8Zq2K5bBC0yJGFEgfrKRJTqt0Um0SC1Rt8drS2wcN6UA3h4LgsL3f1pMcmRSvKucbJE8Qdg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/@wdio/cli/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/karma-script-launcher": { + "version": "1.0.0", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/@wdio/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/karma-sinon": { + "version": "1.0.5", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.10.0" + }, + "peerDependencies": { + "karma": ">=0.10", + "sinon": "*" } }, - "node_modules/@wdio/cli/node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "node_modules/karma-sourcemap-loader": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz", + "integrity": "sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=14.0.0" + "dependencies": { + "graceful-fs": "^4.2.10" } }, - "node_modules/@wdio/cli/node_modules/yargs": { - "version": "17.7.2", + "node_modules/karma-spec-reporter": { + "version": "0.0.32", "dev": true, "license": "MIT", "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" + "colors": "^1.1.2" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/@wdio/concise-reporter": { - "version": "8.38.2", + "node_modules/karma-webpack": { + "version": "5.0.1", "dev": true, "license": "MIT", "dependencies": { - "@wdio/reporter": "8.38.2", - "@wdio/types": "8.38.2", - "chalk": "^5.0.1", - "pretty-ms": "^7.0.1" + "glob": "^7.1.3", + "minimatch": "^9.0.3", + "webpack-merge": "^4.1.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">= 18" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/@wdio/concise-reporter/node_modules/chalk": { - "version": "5.3.0", + "node_modules/karma-webpack/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "18 || 20 || >=22" } }, - "node_modules/@wdio/config": { - "version": "9.15.0", + "node_modules/karma-webpack/node_modules/glob": { + "version": "7.2.3", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@wdio/logger": "9.15.0", - "@wdio/types": "9.15.0", - "@wdio/utils": "9.15.0", - "deepmerge-ts": "^7.0.3", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" + "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": ">=18.20.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/config/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/karma-webpack/node_modules/glob/node_modules/minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, - "license": "MIT", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=18.20.0" + "node": "*" } }, - "node_modules/@wdio/config/node_modules/@wdio/types": { - "version": "9.15.0", + "node_modules/karma-webpack/node_modules/minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "MIT", "dependencies": { - "@types/node": "^20.1.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=18.20.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/config/node_modules/@wdio/utils": { - "version": "9.15.0", + "node_modules/karma-webpack/node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, - "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.15.0", - "@wdio/types": "9.15.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.1", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=18.20.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@wdio/config/node_modules/chalk": { - "version": "5.4.1", + "node_modules/karma/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@wdio/dot-reporter": { - "version": "9.15.0", + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@wdio/reporter": "9.15.0", - "@wdio/types": "9.15.0", - "chalk": "^5.0.1" - }, - "engines": { - "node": ">=18.20.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/@wdio/dot-reporter/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/karma/node_modules/color-convert": { + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=18.20.0" + "node": ">=7.0.0" } }, - "node_modules/@wdio/dot-reporter/node_modules/@wdio/reporter": { - "version": "9.15.0", + "node_modules/karma/node_modules/color-name": { + "version": "1.1.4", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/karma/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", "dependencies": { - "@types/node": "^20.1.0", - "@wdio/logger": "9.15.0", - "@wdio/types": "9.15.0", - "diff": "^7.0.0", - "object-inspect": "^1.12.0" + "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": ">=18.20.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/dot-reporter/node_modules/@wdio/types": { - "version": "9.15.0", + "node_modules/karma/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=18.20.0" + "node": ">=8" } }, - "node_modules/@wdio/dot-reporter/node_modules/chalk": { - "version": "5.4.1", + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@wdio/dot-reporter/node_modules/diff": { - "version": "7.0.0", + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, "engines": { - "node": ">=0.3.1" + "node": ">=10" } }, - "node_modules/@wdio/globals": { - "version": "9.15.0", + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=18.20.0" - }, - "optionalDependencies": { - "expect-webdriverio": "^5.1.0", - "webdriverio": "9.15.0" + "node": ">=10" } }, - "node_modules/@wdio/globals/node_modules/@vitest/pretty-format": { - "version": "3.2.3", + "node_modules/keycode": { + "version": "2.2.1", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "MIT" }, - "node_modules/@wdio/globals/node_modules/@vitest/snapshot": { - "version": "3.2.3", + "node_modules/keyv": { + "version": "4.5.4", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@vitest/pretty-format": "3.2.3", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "json-buffer": "3.0.1" } }, - "node_modules/@wdio/globals/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/kind-of": { + "version": "6.0.3", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">=0.10.0" } }, - "node_modules/@wdio/globals/node_modules/@wdio/types": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.15.0.tgz", - "integrity": "sha512-hR0Dm9TsrjtgOLWOjUMYTOB1hWIlnDzFgZt7XGOzI9Ig8Qa+TDfZSFaZukGxqLIZS/eGhxpnunSHaTAXwJIxYA==", - "dev": true, + "node_modules/klona": { + "version": "2.0.6", "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "^20.1.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">= 8" } }, - "node_modules/@wdio/globals/node_modules/@wdio/utils": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.15.0.tgz", - "integrity": "sha512-XuT1PE1nh4wwJfQW6IN4UT6+iv0+Yf4zhgMh5et04OX6tfrIXkWdx2SDimghDtRukp9i85DvIGWjdPEoQFQdaA==", + "node_modules/last-run": { + "version": "2.0.0", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.15.0", - "@wdio/types": "9.15.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.1", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">= 10.13.0" } }, - "node_modules/@wdio/globals/node_modules/chalk": { - "version": "5.4.1", + "node_modules/lazystream": { + "version": "1.0.1", "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "dependencies": { + "readable-stream": "^2.0.5" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": ">= 0.6.3" } }, - "node_modules/@wdio/globals/node_modules/expect-webdriverio": { - "version": "5.3.0", + "node_modules/lead": { + "version": "4.0.0", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@vitest/snapshot": "^3.2.1", - "expect": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "lodash.isequal": "^4.5.0" - }, - "engines": { - "node": ">=18 || >=20 || >=22" - }, - "peerDependencies": { - "@wdio/globals": "^9.0.0", - "@wdio/logger": "^9.0.0", - "webdriverio": "^9.0.0" - }, - "peerDependenciesMeta": { - "@wdio/globals": { - "optional": false - }, - "@wdio/logger": { - "optional": false - }, - "webdriverio": { - "optional": false - } + "engines": { + "node": ">=10.13.0" } }, - "node_modules/@wdio/globals/node_modules/htmlfy": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", - "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", + "node_modules/levn": { + "version": "0.4.1", "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } }, - "node_modules/@wdio/globals/node_modules/pathe": { - "version": "2.0.3", + "node_modules/lie": { + "version": "3.3.0", "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "immediate": "~3.0.5" + } }, - "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/liftoff": { + "version": "5.0.1", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "type-fest": "^2.12.2" + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.13.0" } }, - "node_modules/@wdio/globals/node_modules/tinyrainbow": { - "version": "2.0.0", + "node_modules/lines-and-columns": { + "version": "2.0.4", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=14.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.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==", + "node_modules/live-connect-common": { + "version": "4.1.0", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/live-connect-js": { + "version": "7.2.0", + "license": "Apache-2.0", + "dependencies": { + "live-connect-common": "^v4.1.0", + "tiny-hashes": "1.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/livereload-js": { + "version": "2.4.0", "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">=6.11.5" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/@wdio/globals/node_modules/webdriver": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.15.0.tgz", - "integrity": "sha512-JCW5xvhZtL6kjbckdePgVYMOlvWbh22F1VFkIf9pw3prwXI2EHED5Eq/nfDnNfHiqr0AfFKWmIDPziSafrVv4Q==", + "node_modules/loader-utils": { + "version": "2.0.4", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@types/node": "^20.1.0", - "@types/ws": "^8.5.3", - "@wdio/config": "9.15.0", - "@wdio/logger": "9.15.0", - "@wdio/protocols": "9.15.0", - "@wdio/types": "9.15.0", - "@wdio/utils": "9.15.0", - "deepmerge-ts": "^7.0.3", - "undici": "^6.20.1", - "ws": "^8.8.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" }, "engines": { - "node": ">=18.20.0" + "node": ">=8.9.0" } }, - "node_modules/@wdio/globals/node_modules/webdriverio": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.15.0.tgz", - "integrity": "sha512-910g6ktwXdAKGyhgCPGw9BzIKOEBBYMFN1bLwC3bW/3mFlxGHO/n70c7Sg9hrsu9VWTzv6m+1Clf27B9uz4a/Q==", + "node_modules/locate-app": { + "version": "2.4.15", "dev": true, - "license": "MIT", - "optional": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@types/node": "^20.11.30", - "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.15.0", - "@wdio/logger": "9.15.0", - "@wdio/protocols": "9.15.0", - "@wdio/repl": "9.4.4", - "@wdio/types": "9.15.0", - "@wdio/utils": "9.15.0", - "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.6.0", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "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.15.0" - }, + "@promptbook/utils": "0.50.0-10", + "type-fest": "2.13.0", + "userhome": "1.0.0" + } + }, + "node_modules/locate-app/node_modules/type-fest": { + "version": "2.13.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=18.20.0" - }, - "peerDependencies": { - "puppeteer-core": ">=22.x || <=24.x" + "node": ">=12.20" }, - "peerDependenciesMeta": { - "puppeteer-core": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/local-runner": { - "version": "9.15.0", + "node_modules/locate-path": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0", - "@wdio/logger": "9.15.0", - "@wdio/repl": "9.4.4", - "@wdio/runner": "9.15.0", - "@wdio/types": "9.15.0", - "async-exit-hook": "^2.0.1", - "split2": "^4.1.0", - "stream-buffers": "^3.0.2" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=18.20.0" + "node": ">=8" } }, - "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + }, + "node_modules/lodash.clone": { + "version": "4.5.0", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "license": "MIT" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.pickby": { + "version": "4.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.0.1" }, "engines": { - "node": ">=18.20.0" + "node": ">=4" } }, - "node_modules/@wdio/local-runner/node_modules/@wdio/types": { - "version": "9.15.0", + "node_modules/log4js": { + "version": "6.9.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@types/node": "^20.1.0" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" }, "engines": { - "node": ">=18.20.0" + "node": ">=8.0" } }, - "node_modules/@wdio/local-runner/node_modules/chalk": { - "version": "5.4.1", + "node_modules/logform": { + "version": "2.6.0", "dev": true, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": ">= 12.0.0" } }, - "node_modules/@wdio/logger": { - "version": "8.38.0", + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.1.90" } }, - "node_modules/@wdio/logger/node_modules/chalk": { - "version": "5.3.0", + "node_modules/loglevel": { + "version": "1.9.1", "dev": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">= 0.6.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" } }, - "node_modules/@wdio/mocha-framework": { - "version": "9.12.6", + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "dev": true, + "license": "MIT" + }, + "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, "license": "MIT", "dependencies": { - "@types/mocha": "^10.0.6", - "@types/node": "^20.11.28", - "@wdio/logger": "9.4.4", - "@wdio/types": "9.12.6", - "@wdio/utils": "9.12.6", - "mocha": "^10.3.0" + "js-tokens": "^3.0.0 || ^4.0.0" }, - "engines": { - "node": ">=18.20.0" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/@wdio/mocha-framework/node_modules/@wdio/logger": { - "version": "9.4.4", + "node_modules/loupe": { + "version": "2.3.7", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18.20.0" + "get-func-name": "^2.0.1" } }, - "node_modules/@wdio/mocha-framework/node_modules/@wdio/types": { - "version": "9.12.6", - "dev": true, - "license": "MIT", + "node_modules/lru-cache": { + "version": "5.1.1", + "license": "ISC", "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": ">=18.20.0" + "yallist": "^3.0.2" } }, - "node_modules/@wdio/mocha-framework/node_modules/chalk": { - "version": "5.4.1", + "node_modules/lru-queue": { + "version": "0.1.0", "dev": true, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "dependencies": { + "es5-ext": "~0.10.2" } }, - "node_modules/@wdio/protocols": { - "version": "9.15.0", + "node_modules/m3u8-parser": { + "version": "4.8.0", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } }, - "node_modules/@wdio/repl": { - "version": "9.4.4", + "node_modules/magic-string": { + "version": "0.30.17", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": ">=18.20.0" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/@wdio/reporter": { - "version": "8.38.2", + "node_modules/make-dir": { + "version": "3.1.0", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.38.2", - "diff": "^5.0.0", - "object-inspect": "^1.12.0" + "semver": "^6.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner": { - "version": "9.15.0", + "node_modules/map-cache": { + "version": "0.2.2", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "^20.11.28", - "@wdio/config": "9.15.0", - "@wdio/dot-reporter": "9.15.0", - "@wdio/globals": "9.15.0", - "@wdio/logger": "9.15.0", - "@wdio/types": "9.15.0", - "@wdio/utils": "9.15.0", - "deepmerge-ts": "^7.0.3", - "expect-webdriverio": "^5.1.0", - "webdriver": "9.15.0", - "webdriverio": "9.15.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/@vitest/pretty-format": { - "version": "3.2.3", + "node_modules/map-stream": { + "version": "0.0.7", "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@wdio/runner/node_modules/@vitest/snapshot": { - "version": "3.2.3", - "dev": true, + "node_modules/media-typer": { + "version": "0.3.0", "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.3", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@wdio/runner/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/memoizee": { + "version": "0.4.17", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "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": ">=18.20.0" + "node": ">=0.12" } }, - "node_modules/@wdio/runner/node_modules/@wdio/types": { - "version": "9.15.0", + "node_modules/memory-fs": { + "version": "0.5.0", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" }, "engines": { - "node": ">=18.20.0" + "node": ">=4.3.0 <5.0.0 || >=5.10" } }, - "node_modules/@wdio/runner/node_modules/@wdio/utils": { - "version": "9.15.0", - "dev": true, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.15.0", - "@wdio/types": "9.15.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.1", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">= 8" } }, - "node_modules/@wdio/runner/node_modules/chalk": { - "version": "5.4.1", - "dev": true, + "node_modules/methods": { + "version": "1.1.2", "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/@wdio/runner/node_modules/expect-webdriverio": { - "version": "5.3.0", + "node_modules/micromatch": { + "version": "4.0.8", "dev": true, "license": "MIT", "dependencies": { - "@vitest/snapshot": "^3.2.1", - "expect": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "lodash.isequal": "^4.5.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=18 || >=20 || >=22" - }, - "peerDependencies": { - "@wdio/globals": "^9.0.0", - "@wdio/logger": "^9.0.0", - "webdriverio": "^9.0.0" - }, - "peerDependenciesMeta": { - "@wdio/globals": { - "optional": false - }, - "@wdio/logger": { - "optional": false - }, - "webdriverio": { - "optional": false - } + "node": ">=8.6" } }, - "node_modules/@wdio/runner/node_modules/htmlfy": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", - "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@wdio/runner/node_modules/pathe": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "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/mime": { + "version": "2.6.0", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^2.12.2" + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0.0" } }, - "node_modules/@wdio/runner/node_modules/tinyrainbow": { - "version": "2.0.0", - "dev": true, + "node_modules/mime-db": { + "version": "1.52.0", "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">= 0.6" } }, - "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, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@wdio/runner/node_modules/webdriver": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.15.0.tgz", - "integrity": "sha512-JCW5xvhZtL6kjbckdePgVYMOlvWbh22F1VFkIf9pw3prwXI2EHED5Eq/nfDnNfHiqr0AfFKWmIDPziSafrVv4Q==", + "node_modules/min-document": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0", - "@types/ws": "^8.5.3", - "@wdio/config": "9.15.0", - "@wdio/logger": "9.15.0", - "@wdio/protocols": "9.15.0", - "@wdio/types": "9.15.0", - "@wdio/utils": "9.15.0", - "deepmerge-ts": "^7.0.3", - "undici": "^6.20.1", - "ws": "^8.8.0" + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=18.20.0" + "node": "*" } }, - "node_modules/@wdio/runner/node_modules/webdriverio": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.15.0.tgz", - "integrity": "sha512-910g6ktwXdAKGyhgCPGw9BzIKOEBBYMFN1bLwC3bW/3mFlxGHO/n70c7Sg9hrsu9VWTzv6m+1Clf27B9uz4a/Q==", + "node_modules/minimist": { + "version": "1.2.8", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "^20.11.30", - "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.15.0", - "@wdio/logger": "9.15.0", - "@wdio/protocols": "9.15.0", - "@wdio/repl": "9.4.4", - "@wdio/types": "9.15.0", - "@wdio/utils": "9.15.0", - "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.6.0", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "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.15.0" - }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", "engines": { - "node": ">=18.20.0" - }, - "peerDependencies": { - "puppeteer-core": ">=22.x || <=24.x" - }, - "peerDependenciesMeta": { - "puppeteer-core": { - "optional": true - } + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@wdio/spec-reporter": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.43.0.tgz", - "integrity": "sha512-Qy5LsGrGHbXJdj2PveNV7CD5g1XpLPvd7wH8bAd9OtuZRTPknyZqYSvB8TlZIV/SfiNlWEJ/05mI/FcixNJ6Xg==", + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", "dev": true, "license": "MIT", "dependencies": { - "@wdio/reporter": "8.43.0", - "@wdio/types": "8.41.0", - "chalk": "^5.1.2", - "easy-table": "^1.2.0", - "pretty-ms": "^7.0.0" + "minimist": "^1.2.6" }, - "engines": { - "node": "^16.13 || >=18" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/@wdio/spec-reporter/node_modules/@types/node": { - "version": "22.18.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", - "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.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", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.43.0.tgz", - "integrity": "sha512-0ph8SabdMrgDmuLUEwA7yvkrvlCw4/EXttb3CGucjSkuiSbZNLhdTMXpyPoewh2soa253fIpnx79HztOsOzn5Q==", + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^22.2.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.41.0", - "diff": "^7.0.0", - "object-inspect": "^1.12.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.41.0.tgz", - "integrity": "sha512-t4NaNTvJZci3Xv/yUZPH4eTL0hxrVTf5wdwNnYIBrzMnlRDbNefjQ0P7FM7ZjQCLaH92AEH6t/XanUId7Webug==", + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^22.2.0" - }, - "engines": { - "node": "^16.13 || >=18" + "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/spec-reporter/node_modules/chalk": { - "version": "5.3.0", + "node_modules/mocha/node_modules/chalk": { + "version": "4.1.2", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/spec-reporter/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "node_modules/mocha/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.3.1" + "node": ">=8" } }, - "node_modules/@wdio/spec-reporter/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } }, - "node_modules/@wdio/types": { - "version": "8.38.2", + "node_modules/mocha/node_modules/color-convert": { + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "^20.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=7.0.0" } }, - "node_modules/@wdio/utils": { - "version": "9.12.6", + "node_modules/mocha/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.4.4", - "@wdio/types": "9.12.6", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.1", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" - }, "engines": { - "node": ">=18.20.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/@wdio/logger": { - "version": "9.4.4", + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=18.20.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/@wdio/types": { - "version": "9.12.6", + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@types/node": "^20.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=18.20.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/utils/node_modules/chalk": { - "version": "5.4.1", + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", "dev": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "node_modules/mocha/node_modules/is-unicode-supported": { + "version": "0.1.0", "dev": true, "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "node_modules/mocha/node_modules/log-symbols": { + "version": "4.1.0", "dev": true, "license": "MIT", "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@xtuc/long": "4.2.2" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", "dev": true, "license": "MIT" }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", "dev": true, "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, "engines": { - "node": ">=10.0.0" + "node": ">=10" } }, - "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, - "license": "BSD-3-Clause" - }, - "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==", + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.9", "dev": true, - "license": "Apache-2.0" + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "node_modules/@zip.js/zip.js": { - "version": "2.7.60", + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "bun": ">=0.7.0", - "deno": ">=1.0.0", - "node": ">=16.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/abbrev": { - "version": "1.0.9", + "node_modules/moment": { + "version": "2.30.1", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": "*" + } }, - "node_modules/abort-controller": { - "version": "3.0.0", + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", "dev": true, "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" }, "engines": { - "node": ">=6.5" + "node": ">= 0.8.0" } }, - "node_modules/accepts": { - "version": "1.3.8", + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "ms": "2.0.0" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "ee-first": "1.1.1" }, "engines": { - "node": ">=0.4.0" + "node": ">= 0.8" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "node_modules/mpd-parser": { + "version": "0.22.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "@xmldom/xmldom": "^0.8.3", + "global": "^4.4.0" }, - "peerDependencies": { - "acorn": "^8.14.0" + "bin": { + "mpd-to-m3u8-json": "bin/parse.js" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", + "node_modules/mrmime": { + "version": "2.0.0", "dev": true, "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">=10" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/multimatch": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", + "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", "dev": true, - "license": "MIT", + "dependencies": { + "array-differ": "^4.0.0", + "array-union": "^3.0.1", + "minimatch": "^9.0.3" + }, "engines": { - "node": ">=0.4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/aes-decrypter": { - "version": "3.1.3", + "node_modules/multimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.5", - "global": "^4.4.0", - "pkcs7": "^1.0.4" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/agent-base": { - "version": "6.0.2", + "node_modules/multimatch/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, - "license": "MIT", "dependencies": { - "debug": "4" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 6.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/multimatch/node_modules/minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "MIT", "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" + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mute-stdout": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/mux.js": { + "version": "6.0.1", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "ajv": "^8.0.0" + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" }, - "peerDependencies": { - "ajv": "^8.0.0" + "bin": { + "muxjs-transmux": "bin/transmux.js" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">=8", + "npm": ">=5" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://opencollective.com/napi-postinstall" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", + "node_modules/natural-compare": { + "version": "1.4.0", "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } + "license": "MIT" }, - "node_modules/amdefine": { - "version": "1.0.1", - "dev": true, - "license": "BSD-3-Clause OR MIT", - "optional": true, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", "engines": { - "node": ">=0.4.2" + "node": ">= 0.6" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", + "node_modules/neo-async": { + "version": "2.6.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/ansi-cyan": { - "version": "0.1.1", + "node_modules/neostandard": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/neostandard/-/neostandard-0.12.2.tgz", + "integrity": "sha512-VZU8EZpSaNadp3rKEwBhVD1Kw8jE3AftQLkCyOaM7bWemL1LwsYRsBnAmXy2LjG9zO8t66qJdqB7ccwwORyrAg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-wrap": "0.1.0" + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@stylistic/eslint-plugin": "2.11.0", + "eslint-import-resolver-typescript": "^3.10.1", + "eslint-plugin-import-x": "^4.16.1", + "eslint-plugin-n": "^17.20.0", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-react": "^7.37.5", + "find-up": "^5.0.0", + "globals": "^15.15.0", + "peowly": "^1.3.2", + "typescript-eslint": "^8.35.1" + }, + "bin": { + "neostandard": "cli.mjs" }, "engines": { - "node": ">=0.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.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==", + "node_modules/neostandard/node_modules/find-up": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/neostandard/node_modules/globals": { + "version": "15.15.0", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-gray": { - "version": "0.1.1", + "node_modules/neostandard/node_modules/locate-path": { + "version": "6.0.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-wrap": "0.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-red": { - "version": "0.1.1", + "node_modules/neostandard/node_modules/p-limit": { + "version": "3.1.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-wrap": "0.1.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", + "node_modules/neostandard/node_modules/p-locate": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-wrap": { + "node_modules/neostandard/node_modules/yocto-queue": { "version": "0.1.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "node": ">=10" }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/archiver": { - "version": "7.0.1", + "node_modules/netmask": { + "version": "2.0.2", "dev": true, "license": "MIT", - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, "engines": { - "node": ">= 14" + "node": ">= 0.4.0" } }, - "node_modules/archiver-utils": { - "version": "5.0.2", + "node_modules/next-tick": { + "version": "1.1.0", "dev": true, - "license": "MIT", + "license": "ISC" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/nise": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", "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" + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" } }, - "node_modules/archiver-utils/node_modules/buffer": { - "version": "6.0.3", + "node_modules/nise/node_modules/path-to-regexp": { + "version": "8.2.0", "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" - } - ], "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "engines": { + "node": ">=16" } }, - "node_modules/archiver-utils/node_modules/is-stream": { - "version": "2.0.1", + "node_modules/node-domexception": { + "version": "1.0.0", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.5.0" } }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "4.5.2", + "node_modules/node-fetch": { + "version": "3.3.2", "dev": true, "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.3.0", + "node_modules/node-html-parser": { + "version": "6.1.13", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "css-select": "^5.1.0", + "he": "1.2.0" } }, - "node_modules/archiver/node_modules/async": { - "version": "3.2.6", - "dev": true, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, - "node_modules/archiver/node_modules/buffer": { - "version": "6.0.3", + "node_modules/node-request-interceptor": { + "version": "0.6.3", "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" - } - ], "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "@open-draft/until": "^1.0.3", + "debug": "^4.3.0", + "headers-utils": "^1.2.0", + "strict-event-emitter": "^0.1.0" } }, - "node_modules/archiver/node_modules/buffer-crc32": { - "version": "1.0.0", - "dev": true, - "license": "MIT", + "node_modules/node.extend": { + "version": "2.0.2", + "license": "(MIT OR GPL-2.0)", + "dependencies": { + "has": "^1.0.3", + "is": "^3.2.1" + }, "engines": { - "node": ">=8.0.0" + "node": ">=0.4.0" } }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "4.5.2", + "node_modules/nopt": { + "version": "3.0.6", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "abbrev": "1" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "bin": { + "nopt": "bin/nopt.js" } }, - "node_modules/archiver/node_modules/string_decoder": { - "version": "1.3.0", + "node_modules/normalize-package-data": { + "version": "6.0.1", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "safe-buffer": "~5.2.0" + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/are-docs-informative": { - "version": "0.0.2", + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.6.2", "dev": true, - "license": "MIT", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=14" + "node": ">=10" } }, - "node_modules/argparse": { - "version": "1.0.10", + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/aria-query": { - "version": "5.3.0", + "node_modules/now-and-later": { + "version": "3.0.0", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "dequal": "^2.0.3" + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "node_modules/arr-diff": { - "version": "1.1.0", + "node_modules/npm-run-path": { + "version": "2.0.2", "dev": true, "license": "MIT", "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" + "path-key": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/arr-diff/node_modules/array-slice": { - "version": "0.2.3", + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/arr-flatten": { - "version": "1.1.0", + "node_modules/nth-check": { + "version": "2.1.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/arr-union": { - "version": "2.1.0", + "node_modules/object-assign": { + "version": "4.1.1", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "dev": true, + "node_modules/object-inspect": { + "version": "1.13.4", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, "engines": { "node": ">= 0.4" }, @@ -6366,41 +17097,40 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-differ": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", - "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", + "node_modules/object-is": { + "version": "1.1.6", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-each": { - "version": "1.0.1", + "node_modules/object-keys": { + "version": "1.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/array-includes": { - "version": "3.1.8", + "node_modules/object.assign": { + "version": "4.1.7", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "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" + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -6409,39 +17139,45 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-slice": { + "node_modules/object.defaults": { "version": "1.1.0", "dev": true, "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.4" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "node_modules/object.fromentries": { + "version": "2.0.8", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6450,51 +17186,39 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", + "node_modules/object.groupby": { + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-abstract": "^1.23.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", + "node_modules/object.pick": { + "version": "1.3.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "isobject": "^3.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", + "node_modules/object.values": { + "version": "1.2.1", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6503,35 +17227,85 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" + "wrappy": "1" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/opn": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", + "node_modules/opn/node_modules/is-wsl": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6540,1451 +17314,1304 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/assert": { - "version": "2.1.0", + "node_modules/p-finally": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/assertion-error": { - "version": "1.1.0", + "node_modules/p-locate": { + "version": "4.1.0", "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/ast-types": { - "version": "0.13.4", + "node_modules/pac-proxy-agent": { + "version": "7.2.0", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.0.1" + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" }, "engines": { - "node": ">=4" + "node": ">= 14" } }, - "node_modules/async": { - "version": "1.5.2", + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 14" + } }, - "node_modules/async-done": { - "version": "2.0.0", + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", "dev": true, "license": "MIT", "dependencies": { - "end-of-stream": "^1.4.4", - "once": "^1.4.0", - "stream-exhaust": "^1.0.2" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 14" } }, - "node_modules/async-exit-hook": { - "version": "2.0.1", + "node_modules/pac-resolver": { + "version": "7.0.1", "dev": true, "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 14" } }, - "node_modules/async-function": { + "node_modules/package-json-from-dist": { "version": "1.0.0", "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "node_modules/async-settle": { - "version": "2.0.0", + "node_modules/parse-filepath": { + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "async-done": "^2.0.0" + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=0.8" } }, - "node_modules/asynckit": { - "version": "0.4.0", + "node_modules/parse-imports": { + "version": "2.2.1", "dev": true, - "license": "MIT" + "license": "Apache-2.0 AND MIT", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } }, - "node_modules/atob": { - "version": "2.1.2", + "node_modules/parse-json": { + "version": "7.1.1", "dev": true, - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" }, "engines": { - "node": ">= 4.5.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", + "node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">= 0.4" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "node_modules/parse-ms": { + "version": "2.1.0", "dev": true, "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "engines": { + "node": ">=6" } }, - "node_modules/b4a": { - "version": "1.6.6", + "node_modules/parse-node-version": { + "version": "1.0.1", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "engines": { + "node": ">= 0.10" + } }, - "node_modules/babel-loader": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", - "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "node_modules/parse-passwd": { + "version": "1.0.0", "dev": true, "license": "MIT", - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.4", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" + "node": ">=0.10.0" } }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.1.2", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" + "entities": "^4.4.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" + "parse5": "^7.0.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.1.2", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" + "entities": "^4.4.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", + "node_modules/parseurl": { + "version": "1.3.3", "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/bach": { - "version": "2.0.1", + "node_modules/path-exists": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "async-done": "^2.0.0", - "async-settle": "^2.0.0", - "now-and-later": "^3.0.0" - }, "engines": { - "node": ">=10.13.0" + "node": ">=8" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/bare-events": { - "version": "2.5.4", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, - "node_modules/bare-fs": { - "version": "4.1.2", + "node_modules/path-is-absolute": { + "version": "1.0.1", "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" - }, + "license": "MIT", "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/bare-os": { - "version": "3.6.1", + "node_modules/path-key": { + "version": "3.1.1", "dev": true, - "license": "Apache-2.0", - "optional": true, + "license": "MIT", "engines": { - "bare": ">=1.14.0" + "node": ">=8" } }, - "node_modules/bare-path": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" }, - "node_modules/bare-stream": { - "version": "2.6.5", + "node_modules/path-root": { + "version": "0.1.1", "dev": true, - "license": "Apache-2.0", - "optional": true, + "license": "MIT", "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" + "path-root-regex": "^0.1.0" }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } + "engines": { + "node": ">=0.10.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", + "node_modules/path-root-regex": { + "version": "0.1.2", "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" - } - ], - "license": "MIT" - }, - "node_modules/base64id": { - "version": "2.0.0", "license": "MIT", "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.17.tgz", - "integrity": "sha512-j5zJcx6golJYTG6c05LUZ3Z8Gi+M62zRT/ycz4Xq4iCOdpcxwg7ngEYD4KA0eWZC7U17qh/Smq8bYbACJ0ipBA==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "node": ">=0.10.0" } }, - "node_modules/basic-auth": { - "version": "2.0.1", + "node_modules/path-scurry": { + "version": "1.11.1", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "safe-buffer": "5.1.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", "dev": true, "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.0.5", + "node_modules/pathval": { + "version": "1.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": "*" } }, - "node_modules/batch": { - "version": "0.6.1", + "node_modules/pend": { + "version": "1.2.0", "dev": true, "license": "MIT" }, - "node_modules/big.js": { - "version": "5.2.2", + "node_modules/peowly": { + "version": "1.3.2", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=18.6.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" }, - "node_modules/binaryextensions": { - "version": "2.3.0", + "node_modules/picomatch": { + "version": "2.3.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=8.6" }, "funding": { - "url": "https://bevry.me/fund" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/bl": { - "version": "5.1.0", + "node_modules/pirates": { + "version": "4.0.6", "dev": true, "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "engines": { + "node": ">= 6" } }, - "node_modules/bl/node_modules/buffer": { - "version": "6.0.3", + "node_modules/pkcs7": { + "version": "1.0.4", "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" - } - ], - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "@babel/runtime": "^7.5.5" + }, + "bin": { + "pkcs7": "bin/cli.js" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", + "node_modules/pkg-dir": { + "version": "4.2.0", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "find-up": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "license": "MIT" - }, - "node_modules/body": { - "version": "5.1.0", + "node_modules/plugin-error": { + "version": "2.0.1", "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "ansi-colors": "^1.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">=10.13.0" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/body/node_modules/bytes": { - "version": "1.0.0", - "dev": true - }, - "node_modules/body/node_modules/raw-body": { - "version": "1.1.7", + "node_modules/plugin-error/node_modules/ansi-colors": { + "version": "1.1.0", "dev": true, "license": "MIT", "dependencies": { - "bytes": "1", - "string_decoder": "0.10" + "ansi-wrap": "^0.1.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=0.10.0" } }, - "node_modules/body/node_modules/string_decoder": { - "version": "0.10.31", - "dev": true, - "license": "MIT" - }, - "node_modules/boolbase": { + "node_modules/possible-typed-array-names": { "version": "1.0.0", "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 0.4" } }, - "node_modules/braces": { - "version": "3.0.3", + "node_modules/postcss": { + "version": "7.0.39", + "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" }, "engines": { - "node": ">=8" + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", + "node_modules/postcss/node_modules/picocolors": { + "version": "0.2.1", "dev": true, "license": "ISC" }, - "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserstack": { - "version": "1.5.3", + "node_modules/prelude-ls": { + "version": "1.2.1", "dev": true, "license": "MIT", - "dependencies": { - "https-proxy-agent": "^2.2.1" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/browserstack-local": { - "version": "1.5.5", + "node_modules/prettier": { + "version": "2.8.1", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", + "node_modules/pretty-format": { + "version": "29.7.0", "dev": true, "license": "MIT", "dependencies": { - "es6-promisify": "^5.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 4.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", + "node_modules/pretty-ms": { + "version": "7.0.1", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "parse-ms": "^2.1.0" }, "engines": { - "node": ">= 4.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", + "node_modules/process": { + "version": "0.11.10", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.6.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, + "node_modules/process-nextick-args": { + "version": "2.0.1", "license": "MIT" }, - "node_modules/bufferstreams": { - "version": "1.0.1", - "dependencies": { - "readable-stream": "^1.0.33" - }, + "node_modules/progress": { + "version": "2.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.10.0" + "node": ">=0.4.0" } }, - "node_modules/bufferstreams/node_modules/isarray": { - "version": "0.0.1", - "license": "MIT" - }, - "node_modules/bufferstreams/node_modules/readable-stream": { - "version": "1.1.14", + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "node_modules/bufferstreams/node_modules/string_decoder": { - "version": "0.10.31", + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, - "node_modules/bytes": { - "version": "3.1.2", + "node_modules/proxy-addr": { + "version": "2.0.7", "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "node_modules/call-bind": { - "version": "1.0.8", + "node_modules/proxy-agent": { + "version": "6.5.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 14" } }, - "node_modules/call-bound": { - "version": "1.0.4", + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/callsites": { - "version": "3.1.0", + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/camelcase": { - "version": "5.3.1", + "node_modules/proxy-from-env": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/can-autoplay": { - "version": "3.0.2", + "node_modules/prr": { + "version": "1.0.1", "dev": true, "license": "MIT" }, - "node_modules/caniuse-lite": { - "version": "1.0.30001756", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", - "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chai": { - "version": "4.4.1", + "node_modules/pump": { + "version": "3.0.0", "dev": true, "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/chalk": { - "version": "2.4.2", + "node_modules/punycode": { + "version": "2.3.1", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/chardet": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", - "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "node_modules/puppeteer": { + "version": "24.11.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.11.2.tgz", + "integrity": "sha512-HopdRZWHa5zk0HSwd8hU+GlahQ3fmesTAqMIDHVY9HasCvppcYuHYXyjml0nlm+nbwVCqAQWV+dSmiNCrZGTGQ==", "dev": true, - "license": "MIT" + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1464554", + "puppeteer-core": "24.11.2", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/check-error": { - "version": "1.0.3", + "node_modules/puppeteer-core": { + "version": "24.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.11.2.tgz", + "integrity": "sha512-c49WifNb8hix+gQH17TldmD6TC/Md2HBaTJLHexIUq4sZvo2pyHY/Pp25qFQjibksBu/SJRYUY7JsoaepNbiRA==", "dev": true, - "license": "MIT", "dependencies": { - "get-func-name": "^2.0.2" + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1464554", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.3" }, "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/cheerio": { - "version": "1.0.0", + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, - "license": "MIT", "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" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.17" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "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/cheerio-select": { - "version": "2.1.0", + "node_modules/q": { + "version": "1.5.1", "dev": true, - "license": "BSD-2-Clause", - "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" + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" } }, - "node_modules/cheerio/node_modules/parse5": { - "version": "7.1.2", + "node_modules/qjobs": { + "version": "1.2.0", "dev": true, "license": "MIT", - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=0.9" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "license": "MIT", + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "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" + "side-channel": "^1.1.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">=0.6" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } + "license": "MIT" }, - "node_modules/chromium-bidi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", - "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "node_modules/querystringify": { + "version": "2.2.0", "dev": true, - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } + "license": "MIT" }, - "node_modules/ci-info": { - "version": "3.9.0", + "node_modules/queue-microtask": { + "version": "1.2.3", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "safe-buffer": "^5.1.0" } }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", "engines": { - "node": ">= 12" + "node": ">= 0.6" } }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dependencies": { - "color-convert": "^2.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "engines": { - "node": ">=7.0.0" + "node": ">= 0.8" } }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", + "node_modules/react-is": { + "version": "18.3.1", "dev": true, "license": "MIT" }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/read-pkg": { + "version": "8.1.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", + "node_modules/read-pkg-up": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", + "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" }, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone": { - "version": "2.1.2", + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "6.3.0", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, "engines": { - "node": ">=0.8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-buffer": { - "version": "1.0.0", + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "7.2.0", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, "engines": { - "node": ">= 0.10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-deep": { - "version": "4.0.1", + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "6.0.0", "dev": true, "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-stats": { - "version": "1.0.0", + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "5.0.0", "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", "license": "MIT" }, - "node_modules/cloneable-readable": { + "node_modules/readdir-glob": { "version": "1.1.3", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "minimatch": "^5.1.0" } }, - "node_modules/color-convert": { - "version": "1.9.3", + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "balanced-match": "^1.0.0" } }, - "node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/color-support": { - "version": "1.1.3", + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/colors": { - "version": "1.4.0", + "node_modules/readdirp": { + "version": "3.6.0", "dev": true, "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, "engines": { - "node": ">=0.1.90" + "node": ">=8.10.0" } }, - "node_modules/combined-stream": { - "version": "1.0.8", + "node_modules/rechoir": { + "version": "0.8.0", "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 10.13.0" } }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/comment-parser": { - "version": "1.4.1", + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dev": true, "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=6.0.0" } }, - "node_modules/commondir": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/compress-commons": { - "version": "6.0.2", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", "dev": true, "license": "MIT", "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/compress-commons/node_modules/buffer": { - "version": "6.0.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" } }, - "node_modules/compress-commons/node_modules/is-stream": { - "version": "2.0.1", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "4.5.2", - "dev": true, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=4" } }, - "node_modules/compress-commons/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "license": "MIT", + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "license": "BSD-2-Clause", "dependencies": { - "safe-buffer": "~5.2.0" + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "license": "MIT" - }, - "node_modules/concat-with-sourcemaps": { + "node_modules/remove-trailing-separator": { "version": "1.1.0", "dev": true, - "license": "ISC", - "dependencies": { - "source-map": "^0.6.1" - } + "license": "ISC" }, - "node_modules/connect": { - "version": "3.7.0", + "node_modules/replace-ext": { + "version": "2.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, "engines": { - "node": ">= 0.10.0" + "node": ">= 10" } }, - "node_modules/connect-livereload": { - "version": "0.6.1", + "node_modules/replace-homedir": { + "version": "2.0.0", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">= 10.13.0" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", + "node_modules/replacestream": { + "version": "4.0.3", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ms": "2.0.0" + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" } }, - "node_modules/connect/node_modules/finalhandler": { - "version": "1.1.2", + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/connect/node_modules/on-finished": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/connect/node_modules/statuses": { - "version": "1.5.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "node_modules/requires-port": { + "version": "1.0.0", + "dev": true, + "license": "MIT" }, - "node_modules/consolidate": { - "version": "0.15.1", + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { - "bluebird": "^3.1.1" + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/content-disposition": { - "version": "0.5.4", + "node_modules/resolve-dir": { + "version": "1.0.1", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/content-type": { - "version": "1.0.5", + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "dev": true - }, - "node_modules/convert-source-map": { + "node_modules/resolve-options": { "version": "2.0.0", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/copy-props": { - "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "each-props": "^3.0.0", - "is-plain-object": "^5.0.0" + "value-or-function": "^4.0.0" }, "engines": { "node": ">= 10.13.0" } }, - "node_modules/core-js": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", - "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", - "hasInstallScript": true, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/core-js-compat": { - "version": "3.42.0", + "node_modules/resq": { + "version": "1.11.0", + "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "fast-deep-equal": "^2.0.1" } }, - "node_modules/core-util-is": { - "version": "1.0.3", + "node_modules/resq/node_modules/fast-deep-equal": { + "version": "2.0.1", + "dev": true, "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.5", + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, "engines": { - "node": ">= 0.10" + "node": ">=10" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/reusify": { + "version": "1.1.0", "dev": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "node_modules/cosmiconfig/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/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "node_modules/rfdc": { + "version": "1.4.1", "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/cosmiconfig/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 + "license": "MIT" }, - "node_modules/cosmiconfig/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/rgb2hex": { + "version": "0.2.5", + "dev": true, + "license": "MIT" }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/rimraf": { + "version": "3.0.2", "dev": true, + "license": "ISC", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "glob": "^7.1.3" }, - "engines": { - "node": ">=8" + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/crc-32": { - "version": "1.2.2", + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", "dev": true, - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" + "license": "ISC", + "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": ">=0.8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/crc32-stream": { - "version": "6.0.0", + "node_modules/run-async": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.5.tgz", + "integrity": "sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==", "dev": true, "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, "engines": { - "node": ">= 14" + "node": ">=0.12.0" } }, - "node_modules/crc32-stream/node_modules/buffer": { - "version": "6.0.3", + "node_modules/run-parallel": { + "version": "1.2.0", "dev": true, "funding": [ { @@ -8002,585 +18629,465 @@ ], "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "4.5.2", - "dev": true, - "license": "MIT", - "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/crc32-stream/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "queue-microtask": "^1.2.2" } }, - "node_modules/create-wdio": { - "version": "9.18.2", - "resolved": "https://registry.npmjs.org/create-wdio/-/create-wdio-9.18.2.tgz", - "integrity": "sha512-atf81YJfyTNAJXsNu3qhpqF4OO43tHGTpr88duAc1Hk4a0uXJAPUYLnYxshOuMnfmeAxlWD+NqGU7orRiXEuJg==", + "node_modules/rust-result": { + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "commander": "^14.0.0", - "cross-spawn": "^7.0.3", - "ejs": "^3.1.10", - "execa": "^9.6.0", - "import-meta-resolve": "^4.1.0", - "inquirer": "^12.7.0", - "normalize-package-data": "^7.0.0", - "read-pkg-up": "^10.1.0", - "recursive-readdir": "^2.2.3", - "semver": "^7.6.3", - "type-fest": "^4.41.0", - "yargs": "^17.7.2" - }, - "bin": { - "create-wdio": "bin/wdio.js" - }, - "engines": { - "node": ">=12.0.0" + "individual": "^2.0.0" } }, - "node_modules/create-wdio/node_modules/chalk": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", - "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" } }, - "node_modules/create-wdio/node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "node_modules/safaridriver": { + "version": "1.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=20" + "node": ">=18.0.0" } }, - "node_modules/create-wdio/node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "node_modules/safe-array-concat": { + "version": "1.1.3", "dev": true, "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, "engines": { - "node": "^18.19.0 || >=20.5.0" + "node": ">=0.4" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/create-wdio/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==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-json-parse": { + "version": "1.0.1", + "dev": true + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" + "es-errors": "^1.3.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/create-wdio/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/safe-regex-test": { + "version": "1.1.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "lru-cache": "^10.0.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/create-wdio/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, - "license": "MIT", - "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/create-wdio/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/create-wdio/node_modules/normalize-package-data": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.1.tgz", - "integrity": "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==", + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", "dev": true, - "license": "BSD-2-Clause", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "ret": "~0.5.0" } }, - "node_modules/create-wdio/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==", + "node_modules/safe-stable-stringify": { + "version": "2.4.3", "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/create-wdio/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, - "license": "MIT", + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, "engines": { - "node": ">=18" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/create-wdio/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, - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/create-wdio/node_modules/pretty-ms": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", - "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", - "dev": true, - "license": "MIT", + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" + "fast-deep-equal": "^3.1.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/create-wdio/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/semver": { + "version": "6.3.1", "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/create-wdio/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, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/create-wdio/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/semver-greatest-satisfied-range": { + "version": "2.0.0", "dev": true, "license": "MIT", "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" + "sver": "^1.8.3" }, "engines": { - "node": ">=12" + "node": ">= 10.13.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "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": ">= 8" + "node": ">= 0.8.0" } }, - "node_modules/cross-spawn/node_modules/isexe": { + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, + "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==", + "license": "MIT", "bin": { - "node-which": "bin/node-which" + "mime": "cli.js" }, "engines": { - "node": ">= 8" + "node": ">=4" } }, - "node_modules/crypto-js": { - "version": "4.2.0", + "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==", "license": "MIT" }, - "node_modules/css": { - "version": "3.0.0", + "node_modules/serialize-error": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-12.0.0.tgz", + "integrity": "sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "type-fest": "^4.31.0" }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-shorthand-properties": { - "version": "1.1.1", - "dev": true - }, - "node_modules/css-value": { - "version": "0.0.1", - "dev": true - }, - "node_modules/css-what": { - "version": "6.1.0", - "dev": true, - "license": "BSD-2-Clause", "engines": { - "node": ">= 6" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/csv-writer": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "node_modules/custom-event": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", + "node_modules/serialize-javascript": { + "version": "6.0.2", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" + "randombytes": "^2.1.0" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", + "node_modules/serve-index": { + "version": "1.9.1", "dev": true, "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, "engines": { - "node": ">= 12" + "node": ">= 0.8.0" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "ms": "2.0.0" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" + "node": ">= 0.6" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/date-format": { - "version": "4.0.14", - "license": "MIT", - "engines": { - "node": ">=4.0" - } + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "dev": true, + "license": "ISC" }, - "node_modules/debounce": { - "version": "1.2.1", + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.3.6", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug-fabulous": { + "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "dev": true, "license": "MIT", - "dependencies": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" + "engines": { + "node": ">= 0.6" } }, - "node_modules/debug-fabulous/node_modules/debug": { - "version": "3.2.7", - "dev": true, + "node_modules/serve-static": { + "version": "1.16.2", "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/decamelize": { - "version": "6.0.0", - "dev": true, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", + }, + "node_modules/set-function-length": { + "version": "1.2.2", "dev": true, "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">=0.10" + "node": ">= 0.4" } }, - "node_modules/deep-eql": { - "version": "4.1.4", + "node_modules/set-function-name": { + "version": "2.0.2", "dev": true, "license": "MIT", "dependencies": { - "type-detect": "^4.0.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/deep-equal": { - "version": "2.2.3", + "node_modules/set-proto": { + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/setimmediate": { + "version": "1.0.5", "dev": true, "license": "MIT" }, - "node_modules/deepmerge-ts": { - "version": "7.1.5", + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/defaults": { - "version": "1.0.4", + "node_modules/shebang-command": { + "version": "2.0.0", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "clone": "^1.0.2" + "shebang-regex": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", + "node_modules/shebang-regex": { + "version": "3.0.0", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=0.8" + "node": ">=8" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "dev": true, + "node_modules/side-channel": { + "version": "1.1.0", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8589,14 +19096,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "dev": true, + "node_modules/side-channel-list": { + "version": "1.0.0", "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" @@ -8605,636 +19110,584 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "dev": true, + "node_modules/side-channel-map": { + "version": "1.0.1", "license": "MIT", "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator/node_modules/escodegen": { - "version": "2.1.0", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "source-map": "~0.6.1" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator/node_modules/esprima": { - "version": "4.0.1", + "node_modules/signal-exit": { + "version": "3.0.7", "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } + "license": "ISC" }, - "node_modules/degenerator/node_modules/estraverse": { - "version": "5.3.0", + "node_modules/sinon": { + "version": "20.0.0", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.5", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.8" + "node": ">=0.3.1" } }, - "node_modules/dequal": { - "version": "2.0.3", + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=8" } }, - "node_modules/detect-file": { - "version": "1.0.0", + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/detect-newline": { - "version": "2.1.0", + "node_modules/sirv": { + "version": "2.0.4", "dev": true, "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 10" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1464554", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", - "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", - "dev": true - }, - "node_modules/di": { - "version": "0.0.1", - "license": "MIT" - }, - "node_modules/diff": { - "version": "5.2.0", + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.3.1" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", + "node_modules/slashes": { + "version": "3.0.12", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/dlv": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/socket.io": { + "version": "4.8.0", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.2.0" } }, - "node_modules/dom-serialize": { - "version": "2.2.1", + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "dev": true, "license": "MIT", "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "debug": "~4.3.4", + "ws": "~8.17.1" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", + "node_modules/socket.io-parser": { + "version": "4.2.4", "dev": true, "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "engines": { + "node": ">=10.0.0" } }, - "node_modules/dom-walk": { - "version": "0.1.2", - "dev": true - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", + "node_modules/socks": { + "version": "2.8.4", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0" + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/domutils": { - "version": "3.1.0", + "node_modules/socks-proxy-agent": { + "version": "8.0.5", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "engines": { + "node": ">= 14" } }, - "node_modules/dotenv": { - "version": "17.2.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", - "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">= 14" } }, - "node_modules/dset": { - "version": "3.1.4", - "license": "MIT", + "node_modules/source-list-map": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "license": "MIT", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" } }, - "node_modules/duplexer": { - "version": "0.1.2", + "node_modules/source-map-loader/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, - "license": "MIT" + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/duplexify": { - "version": "4.1.3", + "node_modules/source-map-resolve": { + "version": "0.6.0", "dev": true, "license": "MIT", "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" } }, - "node_modules/duplexify/node_modules/readable-stream": { - "version": "3.6.2", + "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, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/each-props": { - "version": "3.0.0", + "node_modules/spacetrim": { + "version": "0.11.25", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/sparkles": { + "version": "2.1.0", "dev": true, "license": "MIT", - "dependencies": { - "is-plain-object": "^5.0.0", - "object.defaults": "^1.1.0" - }, "engines": { "node": ">= 10.13.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/easy-table": { - "version": "1.2.0", + "node_modules/spdx-correct": { + "version": "3.2.0", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^5.0.1" - }, - "optionalDependencies": { - "wcwidth": "^1.0.1" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/edge-paths": { - "version": "3.0.5", + "node_modules/spdx-exceptions": { + "version": "2.5.0", "dev": true, - "license": "MIT", - "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/shirshak55" + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/edge-paths/node_modules/isexe": { - "version": "2.0.0", + "node_modules/spdx-license-ids": { + "version": "3.0.18", "dev": true, - "license": "ISC" + "license": "CC0-1.0" }, - "node_modules/edge-paths/node_modules/which": { - "version": "2.0.2", + "node_modules/split2": { + "version": "4.2.0", "dev": true, "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, "engines": { - "node": ">= 8" + "node": ">= 10.x" } }, - "node_modules/edgedriver": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-6.1.2.tgz", - "integrity": "sha512-UvFqd/IR81iPyWMcxXbUNi+xKWR7JjfoHjfuwjqsj9UHQKn80RpQmS0jf+U25IPi+gKVPcpOSKm0XkqgGMq4zQ==", + "node_modules/sprintf-js": { + "version": "1.0.3", + "license": "BSD-3-Clause" + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@wdio/logger": "^9.1.3", - "@zip.js/zip.js": "^2.7.53", - "decamelize": "^6.0.0", - "edge-paths": "^3.0.5", - "fast-xml-parser": "^5.0.8", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "node-fetch": "^3.3.2", - "which": "^5.0.0" - }, - "bin": { - "edgedriver": "bin/edgedriver.js" - }, "engines": { - "node": ">=18.0.0" + "node": ">=12.0.0" } }, - "node_modules/edgedriver/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/stack-utils": { + "version": "2.0.6", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">=18.20.0" + "node": ">=10" } }, - "node_modules/edgedriver/node_modules/agent-base": { - "version": "7.1.3", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/edgedriver/node_modules/chalk": { - "version": "5.4.1", - "dev": true, + "node_modules/statuses": { + "version": "2.0.1", "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.8" } }, - "node_modules/edgedriver/node_modules/https-proxy-agent": { - "version": "7.0.6", + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "internal-slot": "^1.0.4" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/stream-buffers": { + "version": "3.0.2", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, + "license": "Unlicense", "engines": { - "node": ">=0.10.0" + "node": ">= 0.10.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.237", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", - "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", + "node_modules/stream-composer": { + "version": "1.0.2", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "streamx": "^2.13.2" } }, - "node_modules/encodeurl": { + "node_modules/stream-exhaust": { "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "dev": true, + "license": "MIT" }, - "node_modules/encoding-sniffer": { - "version": "0.2.0", + "node_modules/stream-shift": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/streamfilter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", + "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", "dev": true, - "license": "MIT", "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" + "readable-stream": "^3.0.6" }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + "engines": { + "node": ">=8.12.0" } }, - "node_modules/encoding-sniffer/node_modules/iconv-lite": { - "version": "0.6.3", + "node_modules/streamfilter/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, - "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", + "node_modules/streamroller": { + "version": "3.1.5", "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" } }, - "node_modules/engine.io": { - "version": "6.6.2", + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "dev": true, "license": "MIT", "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=10.2.0" + "node": ">=6 <7 || >=8" } }, - "node_modules/engine.io-parser": { - "version": "5.2.3", + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, "license": "MIT", - "engines": { - "node": ">=10.0.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.7.2", + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 4.0.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "node_modules/streamx": { + "version": "2.22.0", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" }, - "engines": { - "node": ">=10.13.0" + "optionalDependencies": { + "bare-events": "^2.2.0" } }, - "node_modules/ent": { - "version": "2.2.0", + "node_modules/strict-event-emitter": { + "version": "0.1.0", + "dev": true, "license": "MIT" }, - "node_modules/entities": { - "version": "4.5.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/string-template": { + "version": "0.2.1", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/errno": { - "version": "0.1.8", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", "dev": true, "license": "MIT", "dependencies": { - "prr": "~1.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "bin": { - "errno": "cli.js" + "engines": { + "node": ">=8" } }, - "node_modules/error": { - "version": "7.2.1", + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, + "license": "MIT", "dependencies": { - "string-template": "~0.2.1" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/error-ex": { - "version": "1.3.2", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/es-abstract": { - "version": "1.23.9", + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", + "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", "has-symbols": "^1.1.0", - "hasown": "^2.0.2", "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-regex": "^1.2.1", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -9243,2240 +19696,2337 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-define-property": { - "version": "1.0.1", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-errors": { - "version": "1.3.0", + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "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, + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-module-lexer": { - "version": "1.5.3", + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] }, - "node_modules/es-object-atoms": { - "version": "1.1.1", + "node_modules/supports-color": { + "version": "5.5.0", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "dev": true, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", + "node_modules/sver": { + "version": "1.8.4", "dev": true, "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" + "optionalDependencies": { + "semver": "^6.3.0" } }, - "node_modules/es-to-primitive": { - "version": "1.3.0", + "node_modules/synckit": { + "version": "0.9.2", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/unts" } }, - "node_modules/es5-ext": { - "version": "0.10.64", + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, + "license": "MIT", "engines": { - "node": ">=0.10" + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/es6-iterator": { - "version": "2.0.3", + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "dev": true, "license": "MIT", "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "license": "MIT" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", + "node_modules/tar-stream": { + "version": "3.1.7", "dev": true, "license": "MIT", "dependencies": { - "es6-promise": "^4.0.3" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/es6-symbol": { - "version": "3.1.4", + "node_modules/teex": { + "version": "1.0.1", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" + "streamx": "^2.12.5" } }, - "node_modules/es6-weak-map": { - "version": "2.0.3", + "node_modules/ternary-stream": { + "version": "3.0.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "duplexify": "^4.1.1", + "fork-stream": "^0.0.4", + "merge-stream": "^2.0.0", + "through2": "^3.0.1" } }, - "node_modules/esbuild": { - "version": "0.25.2", + "node_modules/ternary-stream/node_modules/through2": { + "version": "3.0.2", "dev": true, - "hasInstallScript": true, "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/terser": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.45.0.tgz", + "integrity": "sha512-hQ9c+JZEnMug8eqzuU48sCeq95f00lLDAaJ5gWhRkFXsfy3+SUkZXiF/Z66ZO6EomSmgqXnkhVrWXKaQ8K41Ug==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, "bin": { - "esbuild": "bin/esbuild" + "terser": "bin/terser" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" + "node": ">=10" } }, - "node_modules/escalade": { - "version": "3.2.0", + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "dev": true, "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, "engines": { - "node": ">=6" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/escape-html": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", + "node_modules/test-exclude": { + "version": "6.0.0", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, "engines": { - "node": ">=0.8.0" + "node": ">=8" } }, - "node_modules/escodegen": { - "version": "1.8.1", + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "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": ">=0.12.0" + "node": "*" }, - "optionalDependencies": { - "source-map": "~0.2.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "1.9.3", + "node_modules/text-decoder": { + "version": "1.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/textextensions": { + "version": "3.3.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" } }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", + "node_modules/through": { + "version": "2.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" + "readable-stream": "3" } }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", + "node_modules/through2/node_modules/readable-stream": { + "version": "3.6.2", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 6" } }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", + "node_modules/time-stamp": { + "version": "1.1.0", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=0.10.0" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.2.0", + "node_modules/timers-ext": { + "version": "0.1.8", "dev": true, - "optional": true, + "license": "ISC", "dependencies": { - "amdefine": ">=0.0.4" + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" }, "engines": { - "node": ">=0.8.0" + "node": ">=0.12" } }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", + "node_modules/tiny-hashes": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/tiny-lr": { + "version": "1.1.1", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" } }, - "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "node_modules/tiny-lr/node_modules/debug": { + "version": "3.2.7", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "ms": "^2.1.1" } }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.4" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=12" + "node": ">=12.0.0" }, - "peerDependencies": { - "eslint": ">=6.0.0" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/eslint-import-context": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", - "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "get-tsconfig": "^4.10.1", - "stable-hash-x": "^0.2.0" - }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint-import-context" - }, - "peerDependencies": { - "unrs-resolver": "^1.0.0" - }, - "peerDependenciesMeta": { - "unrs-resolver": { - "optional": true - } + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", + "node_modules/tinyrainbow": { + "version": "1.2.0", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", + "node_modules/tmp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", + "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">=14.14" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", - "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "node_modules/to-absolute-glob": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-3.0.0.tgz", + "integrity": "sha512-loO/XEWTRqpfcpI7+Jr2RR2Umaaozx1t6OSVWtMi0oy5F/Fxg3IC+D/TToDnxyAGs7uZBGT/6XmyDUxgsObJXA==", "dev": true, - "license": "ISC", "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } + "engines": { + "node": ">=0.10.0" } }, - "node_modules/eslint-import-resolver-typescript/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/to-regex-range": { + "version": "5.0.1", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "is-number": "^7.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8.0" } }, - "node_modules/eslint-import-resolver-typescript/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", + "node_modules/to-through": { + "version": "3.0.0", "dev": true, "license": "MIT", "dependencies": { - "debug": "^3.2.7" + "streamx": "^2.12.5" }, "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": ">=10.13.0" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">=6" } }, - "node_modules/eslint-plugin-chai-friendly": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.1.0.tgz", - "integrity": "sha512-+T1rClpDdXkgBAhC16vRQMI5umiWojVqkj9oUTdpma50+uByCZM/oBfxitZiOkjMRlm725mwFfz/RVgyDRvCKA==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 14.0.0" + } + }, + "node_modules/tryit": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" }, "peerDependencies": { - "eslint": ">=3.0.0" + "typescript": ">=4.8.4" } }, - "node_modules/eslint-plugin-es-x": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "node_modules/ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", "dev": true, "funding": [ - "https://github.com/sponsors/ota-meshi", - "https://opencollective.com/eslint" + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" + } ], - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.11.0", - "eslint-compat-utils": "^0.5.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" + "picomatch": "^4.0.2" }, "peerDependencies": { - "eslint": ">=8" + "typescript": ">=4.0.0" } }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", + "node_modules/ts-declaration-location/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" - }, "engines": { - "node": ">=4" + "node": ">=12" }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/eslint-plugin-import-x": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", - "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.35.0", - "comment-parser": "^1.4.1", - "debug": "^4.4.1", - "eslint-import-context": "^0.1.9", - "is-glob": "^4.0.3", - "minimatch": "^9.0.3 || ^10.0.1", - "semver": "^7.7.2", - "stable-hash-x": "^0.2.0", - "unrs-resolver": "^1.9.2" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.19.3", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-import-x" + "bin": { + "tsx": "dist/cli.mjs" }, - "peerDependencies": { - "@typescript-eslint/utils": "^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "eslint-import-resolver-node": "*" + "engines": { + "node": ">=18.0.0" }, - "peerDependenciesMeta": { - "@typescript-eslint/utils": { - "optional": true - }, - "eslint-import-resolver-node": { - "optional": true - } + "optionalDependencies": { + "fsevents": "~2.3.3" } }, - "node_modules/eslint-plugin-import-x/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/type": { + "version": "2.7.3", + "dev": true, + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.8.0" } }, - "node_modules/eslint-plugin-import-x/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "node_modules/type-detect": { + "version": "4.0.8", "dev": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-import-x/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-import-x/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-jsdoc": { - "version": "50.6.6", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@es-joy/jsdoccomment": "~0.49.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.3.6", - "escape-string-regexp": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.6.0", - "parse-imports": "^2.1.1", - "semver": "^7.6.3", - "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.1" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/typed-array-length": { + "version": "1.0.7", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.7.1", + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.8.2", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "bin": { - "semver": "bin/semver.js" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=10" + "node": ">=14.17" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { - "version": "4.0.0", - "dev": true, + "node_modules/typescript-compare": { + "version": "0.0.2", "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "typescript-logic": "^0.0.0" } }, - "node_modules/eslint-plugin-n": { - "version": "17.21.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.3.tgz", - "integrity": "sha512-MtxYjDZhMQgsWRm/4xYLL0i2EhusWT7itDxlJ80l1NND2AL2Vi5Mvneqv/ikG9+zpran0VsVRXTEHrpLmUZRNw==", + "node_modules/typescript-eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", + "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.5.0", - "enhanced-resolve": "^5.17.1", - "eslint-plugin-es-x": "^7.8.0", - "get-tsconfig": "^4.8.1", - "globals": "^15.11.0", - "globrex": "^0.1.2", - "ignore": "^5.3.2", - "semver": "^7.6.3", - "ts-declaration-location": "^1.0.6" + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": ">=8.23.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/eslint-plugin-n/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "node_modules/typescript-logic": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/typescript-tuple": { + "version": "2.2.1", + "license": "MIT", + "dependencies": { + "typescript-compare": "^0.0.2" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.38", "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" + } + ], "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/uglify-js": { + "version": "3.18.0", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", + "optional": true, "bin": { - "semver": "bin/semver.js" + "uglifyjs": "bin/uglifyjs" }, "engines": { - "node": ">=10" + "node": ">=0.8.0" } }, - "node_modules/eslint-plugin-promise": { - "version": "7.2.1", + "node_modules/unbox-primitive": { + "version": "1.1.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0" + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "node_modules/unc-path-regex": { + "version": "0.1.2", "dev": true, "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/eslint-plugin-react/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/undertaker-registry": { + "version": "2.0.0", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 10.13.0" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "3.0.0", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "fastest-levenshtein": "^1.0.7" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, "engines": { - "node": ">=8.0.0" + "node": ">=18.17" } }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", + "node_modules/undici-types": { + "version": "5.26.5", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=4" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "license": "MIT", + "engines": { + "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, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/color-convert": { + "node_modules/universalify": { "version": "2.0.1", "dev": true, "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">= 10.0.0" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "napi-postinstall": "^0.3.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "bin": { + "update-browserslist-db": "cli.js" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/uri-js": { + "version": "4.4.1", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/url": { + "version": "0.11.3", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", + "node_modules/url-parse": { + "version": "1.5.10", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url-toolkit": { + "version": "2.2.5", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/userhome": { + "version": "1.0.0", + "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", + "node_modules/util": { + "version": "0.12.5", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4.0" } }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", + "node_modules/uuid": { + "version": "9.0.1", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", + "node_modules/v8flags": { + "version": "4.0.1", "dev": true, "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 10.13.0" } }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", + "node_modules/value-or-function": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" } }, - "node_modules/eslint/node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, + "node_modules/vary": { + "version": "1.1.2", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/esniff": { - "version": "2.0.1", + "node_modules/video.js": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.7.tgz", + "integrity": "sha512-T2s3WFAht7Zjr2OSJamND9x9Dn2O+Z5WuHGdh8jI5SYh5mkMdVTQ7vSRmA5PYpjXJ2ycch6jpMjkJEIEU2xxqw==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "2.16.3", + "@videojs/vhs-utils": "^3.0.4", + "@videojs/xhr": "2.6.0", + "aes-decrypter": "3.1.3", + "global": "^4.4.0", + "keycode": "^2.2.0", + "m3u8-parser": "4.8.0", + "mpd-parser": "0.22.1", + "mux.js": "6.0.1", + "safe-json-parse": "4.0.0", + "videojs-font": "3.2.0", + "videojs-vtt.js": "^0.15.5" } }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "node_modules/video.js/node_modules/safe-json-parse": { + "version": "4.0.0", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "rust-result": "^1.0.0" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/videojs-contrib-ads": { + "version": "6.9.0", "dev": true, "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "global": "^4.3.2", + "video.js": "^6 || ^7" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=8", + "npm": ">=5" } }, - "node_modules/esprima": { - "version": "2.7.3", + "node_modules/videojs-font": { + "version": "3.2.0", "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } + "license": "Apache-2.0" }, - "node_modules/esquery": { - "version": "1.6.0", + "node_modules/videojs-ima": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.4.0.tgz", + "integrity": "sha512-pP93nNmsjz+BgRIQ3KnQjiB3hgxsHGLweU+23Aq656C9N632t//4gbrnbDBa3XLosBNXrK4uKxuBTFi/6drKRQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { - "estraverse": "^5.1.0" + "@hapi/cryptiles": "^5.1.0", + "can-autoplay": "^3.0.2", + "extend": ">=3.0.2", + "videojs-contrib-ads": "^6.9.0 || ^7" }, "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node": ">=0.8.0" + }, + "peerDependencies": { + "video.js": "^5.19.2 || ^6 || ^7 || ^8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", + "node_modules/videojs-playlist": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.2.0.tgz", + "integrity": "sha512-Kyx6C5r7zmj6y97RrIlyji8JUEt0kUEfVyB4P6VMyEFVyCGlOlzlgPw2verznBp4uDfjVPPuAJKvNJ7x9O5NJw==", "dev": true, - "license": "BSD-2-Clause", + "license": "Apache-2.0", "dependencies": { - "estraverse": "^5.2.0" + "global": "^4.3.2", + "video.js": "^6 || ^7 || ^8" }, "engines": { - "node": ">=4.0" + "node": ">=4.4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", + "node_modules/videojs-vtt.js": { + "version": "0.15.5", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "license": "Apache-2.0", + "dependencies": { + "global": "^4.3.1" } }, - "node_modules/estraverse": { - "version": "4.3.0", + "node_modules/vinyl": { + "version": "2.2.1", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", "license": "MIT", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.10" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "dev": true, - "license": "MIT", + "node_modules/vinyl-bufferstream": { + "version": "1.0.1", "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" + "bufferstreams": "1.0.1" } }, - "node_modules/event-stream": { - "version": "3.3.4", + "node_modules/vinyl-contents": { + "version": "2.0.0", "dev": true, "license": "MIT", "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" + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/event-stream/node_modules/map-stream": { - "version": "0.1.0", - "dev": true - }, - "node_modules/event-target-shim": { - "version": "5.0.1", + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.1", "dev": true, "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, "engines": { - "node": ">=6" + "node": ">=10.13.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", + "node_modules/vinyl-fs": { + "version": "4.0.2", "dev": true, "license": "MIT", + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, "engines": { - "node": ">=0.8.x" + "node": ">=10.13.0" } }, - "node_modules/execa": { - "version": "1.0.0", + "node_modules/vinyl-fs/node_modules/iconv-lite": { + "version": "0.6.3", "dev": true, "license": "MIT", "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" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.6", + "node_modules/vinyl-fs/node_modules/vinyl": { + "version": "3.0.1", "dev": true, "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" }, "engines": { - "node": ">=4.8" + "node": ">=10.13.0" } }, - "node_modules/execa/node_modules/isexe": { + "node_modules/vinyl-sourcemap": { "version": "2.0.0", "dev": true, - "license": "ISC" - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "dev": true, "license": "MIT", + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "node": ">=10.13.0" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", + "node_modules/vinyl-sourcemap/node_modules/vinyl": { + "version": "3.0.1", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^1.0.0" + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "dev": true, - "license": "MIT", + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "license": "ISC", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", + "node_modules/vinyl/node_modules/replace-ext": { + "version": "1.0.1", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "license": "MIT", + "engines": { + "node": ">= 0.10" } }, - "node_modules/expand-tilde": { - "version": "2.0.2", + "node_modules/void-elements": { + "version": "2.0.1", "dev": true, "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/expect": { - "version": "29.7.0", + "node_modules/wait-port": { + "version": "1.1.0", "dev": true, "license": "MIT", "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" + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/express": { - "version": "4.21.2", + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.10.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", + "node_modules/wait-port/node_modules/chalk": { + "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/express/node_modules/mime": { - "version": "1.6.0", + "node_modules/wait-port/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "color-name": "~1.1.4" }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.4", + "dev": true, "license": "MIT" }, - "node_modules/express/node_modules/send": { - "version": "0.19.0", + "node_modules/wait-port/node_modules/commander": { + "version": "9.5.0", + "dev": true, "license": "MIT", - "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": "^12.20.0 || >=14" } }, - "node_modules/express/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", + "node_modules/wait-port/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/express/node_modules/send/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/ext": { - "version": "1.7.0", + "node_modules/wait-port/node_modules/supports-color": { + "version": "7.2.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "type": "^2.7.2" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "1.1.4", + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, "license": "MIT", "dependencies": { - "kind-of": "^1.1.0" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/extend-shallow/node_modules/kind-of": { - "version": "1.1.0", + "node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 14" } }, - "node_modules/extract-zip": { - "version": "2.0.1", + "node_modules/webdriver": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.2.tgz", + "integrity": "sha512-kw6dSwNzimU8/CkGVlM36pqWHZ7BhCwV4/d8fu6rpIYGeQbPwcNc4M90TfJuzYMA7Au3NdrwT/EVQgVLQ9Ju8Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.19.2", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", + "deepmerge-ts": "^7.0.3", + "https-proxy-agent": "^7.0.6", + "undici": "^6.21.3", + "ws": "^8.8.0" }, "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "node": ">=18.20.0" } }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", + "node_modules/webdriver/node_modules/@wdio/config": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.2.tgz", + "integrity": "sha512-OVCzPQxav0QDk5rktQ6LYARZ5ueUuJXIqTXUpS3A9Jt6PF+ZUI5sbO/y+z+qHQXqDq+LkscmFsmkzgnoHzHcfg==", "dev": true, "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.20.0" } }, - "node_modules/extract-zip/node_modules/yauzl": { - "version": "2.10.0", + "node_modules/webdriver/node_modules/@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", "dev": true, "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" } }, - "node_modules/faker": { - "version": "5.5.3", + "node_modules/webdriver/node_modules/@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", "dev": true, "license": "MIT" }, - "node_modules/fancy-log": { - "version": "2.0.0", + "node_modules/webdriver/node_modules/@wdio/types": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.2.tgz", + "integrity": "sha512-fBI7ljL+YcPXSXUhdk2+zVuz7IYP1aDMTq1eVmMme9GY0y67t0dCNPOt6xkCAEdL5dOcV6D2L1r6Cf/M2ifTvQ==", "dev": true, "license": "MIT", "dependencies": { - "color-support": "^1.1.3" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=18.20.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", + "node_modules/webdriver/node_modules/@wdio/utils": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.2.tgz", + "integrity": "sha512-caimJiTsxDUfXn/gRAzcYTO3RydSl7XzD+QpjfWZYJjzr8a2XfNnj+Vdmr8gG4BSkiVHirW9mFCZeQp2eTD7rA==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">=8.6.0" + "node": ">=18.20.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", + "node_modules/webdriver/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] + "license": "MIT", + "engines": { + "node": ">= 14" + } }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "node_modules/webdriver/node_modules/chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "bin": { - "fxparser": "src/cli/cli.js" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", + "node_modules/webdriver/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, "engines": { - "node": ">= 4.9.1" + "node": ">= 14" } }, - "node_modules/fastq": { - "version": "1.19.1", + "node_modules/webdriverio": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.1.tgz", + "integrity": "sha512-hpGgK6d9QNi3AaLFWIPQaEMqJhXF048XAIsV5i5mkL0kjghV1opcuhKgbbG+7pcn8JSpiq6mh7o3MDYtapw90w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.19.1", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/repl": "9.16.2", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "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.8.1", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^12.0.0", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.19.1" + }, + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "puppeteer-core": ">=22.x || <=24.x" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } } }, - "node_modules/faye-websocket": { - "version": "0.10.0", + "node_modules/webdriverio/node_modules/@wdio/config": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", + "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", "dev": true, "license": "MIT", "dependencies": { - "websocket-driver": ">=0.5.1" + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=18.20.0" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", + "node_modules/webdriverio/node_modules/@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", "dev": true, "license": "MIT", "dependencies": { - "pend": "~1.2.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" } }, - "node_modules/fecha": { - "version": "4.2.3", + "node_modules/webdriverio/node_modules/@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", "dev": true, "license": "MIT" }, - "node_modules/fetch-blob": { - "version": "3.2.0", + "node_modules/webdriverio/node_modules/@wdio/repl": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", + "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], "license": "MIT", "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" + "@types/node": "^20.1.0" }, "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/fetch-blob/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" + "node": ">=18.20.0" } }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "node_modules/webdriverio/node_modules/@wdio/types": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", "dev": true, "license": "MIT", "dependencies": { - "is-unicode-supported": "^2.0.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.20.0" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", + "node_modules/webdriverio/node_modules/@wdio/utils": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", + "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" + "node": ">=18.20.0" } }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/webdriverio/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=10" + "node": ">= 14" } }, - "node_modules/fill-range": { - "version": "7.1.1", + "node_modules/webdriverio/node_modules/chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true, "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/finalhandler": { - "version": "1.3.1", + "node_modules/webdriverio/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">= 0.8" + "node": ">= 14" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", + "node_modules/webdriverio/node_modules/webdriver": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.1.tgz", + "integrity": "sha512-cvccIZ3QaUZxxrA81a3rqqgxKt6VzVrZupMc+eX9J40qfGrV3NtdLb/m4AA1PmeTPGN5O3/4KrzDpnVZM4WUnA==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.19.1", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "deepmerge-ts": "^7.0.3", + "https-proxy-agent": "^7.0.6", + "undici": "^6.21.3", + "ws": "^8.8.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=18.20.0" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", + "node_modules/webpack": { + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, "license": "MIT", "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" }, "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "node_modules/findup-sync": { - "version": "5.0.0", + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", "dev": true, "license": "MIT", "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/fined": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^5.0.0", - "object.defaults": "^1.1.0", - "object.pick": "^1.3.0", - "parse-filepath": "^1.0.2" + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" }, "engines": { "node": ">= 10.13.0" } }, - "node_modules/flagged-respawn": { - "version": "2.0.0", + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", "dev": true, "license": "MIT", "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" + "node": ">= 10" } }, - "node_modules/flat-cache": { - "version": "4.0.1", + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, "engines": { - "node": ">=16" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flatted": { - "version": "3.3.1", - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "dev": true, "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { - "debug": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { "optional": true } } }, - "node_modules/for-each": { - "version": "0.3.5", + "node_modules/webpack-manifest-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.1.tgz", + "integrity": "sha512-xTlX7dC3hrASixA2inuWFMz6qHsNi6MT3Uiqw621sJjRTShtpMjbDYhPPZBwWUKdIYKIjSq9em6+uzWayf38aQ==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.2.7" + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "webpack": "^5.75.0" } }, - "node_modules/for-own": { - "version": "1.0.0", + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", "dev": true, "license": "MIT", "dependencies": { - "for-in": "^1.0.1" + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/foreground-child": { - "version": "3.3.0", + "node_modules/webpack-merge": { + "version": "4.2.2", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "lodash": "^4.17.15" } }, - "node_modules/fork-stream": { - "version": "0.0.4", - "dev": true, - "license": "BSD" - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, - "node_modules/formdata-node": { - "version": "5.0.1", + "node_modules/webpack-stream": { + "version": "7.0.0", "dev": true, "license": "MIT", "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" + "fancy-log": "^1.3.3", + "lodash.clone": "^4.3.2", + "lodash.some": "^4.2.2", + "memory-fs": "^0.5.0", + "plugin-error": "^1.0.1", + "supports-color": "^8.1.1", + "through": "^2.3.8", + "vinyl": "^2.2.1" }, "engines": { - "node": ">= 14.17" + "node": ">= 10.0.0" + }, + "peerDependencies": { + "webpack": "^5.21.2" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", + "node_modules/webpack-stream/node_modules/ansi-colors": { + "version": "1.1.0", "dev": true, "license": "MIT", "dependencies": { - "fetch-blob": "^3.1.2" + "ansi-wrap": "^0.1.0" }, "engines": { - "node": ">=12.20.0" + "node": ">=0.10.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", + "node_modules/webpack-stream/node_modules/arr-diff": { + "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/fresh": { - "version": "0.5.2", + "node_modules/webpack-stream/node_modules/arr-union": { + "version": "3.1.0", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/from": { - "version": "0.1.7", - "dev": true, - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", - "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "node_modules/webpack-stream/node_modules/extend-shallow": { + "version": "3.0.2", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "engines": { - "node": ">=14.14" + "node": ">=0.10.0" } }, - "node_modules/fs-mkdirp-stream": { - "version": "2.0.1", + "node_modules/webpack-stream/node_modules/fancy-log": { + "version": "1.3.3", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.8", - "streamx": "^2.12.0" + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" }, "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/fs-readfile-promise": { - "version": "3.0.1", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.10" } }, - "node_modules/fun-hooks": { - "version": "1.1.0", + "node_modules/webpack-stream/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "typescript-tuple": "^2.2.1" + "engines": { + "node": ">=8" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/webpack-stream/node_modules/plugin-error": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", + "node_modules/webpack-stream/node_modules/supports-color": { + "version": "8.1.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/geckodriver": { - "version": "5.0.0", + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", "dev": true, - "hasInstallScript": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@wdio/logger": "^9.1.3", - "@zip.js/zip.js": "^2.7.53", - "decamelize": "^6.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "node-fetch": "^3.3.2", - "tar-fs": "^3.0.6", - "which": "^5.0.0" - }, - "bin": { - "geckodriver": "bin/geckodriver.js" + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=0.8.0" } }, - "node_modules/geckodriver/node_modules/@wdio/logger": { - "version": "9.15.0", + "node_modules/websocket-extensions": { + "version": "0.1.4", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "iconv-lite": "0.6.3" }, "engines": { - "node": ">=18.20.0" + "node": ">=18" } }, - "node_modules/geckodriver/node_modules/agent-base": { - "version": "7.1.3", + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", "dev": true, "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { - "node": ">= 14" + "node": ">=0.10.0" } }, - "node_modules/geckodriver/node_modules/chalk": { - "version": "5.4.1", + "node_modules/whatwg-mimetype": { + "version": "4.0.0", "dev": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=18" } }, - "node_modules/geckodriver/node_modules/https-proxy-agent": { - "version": "7.0.6", + "node_modules/which": { + "version": "5.0.0", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">= 14" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "dev": true, "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "license": "ISC", + "node_modules/which-builtin-type": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-func-name": { - "version": "2.0.2", + "node_modules/which-collection": { + "version": "1.0.2", "dev": true, "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", + "node_modules/which-typed-array": { + "version": "1.1.19", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11485,283 +22035,313 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", + "node_modules/winston-transport": { + "version": "4.7.0", "dev": true, "license": "MIT", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, "engines": { - "node": ">=8.0.0" + "node": ">= 12.0.0" } }, - "node_modules/get-port": { - "version": "7.1.0", + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", "dev": true, "license": "MIT", - "engines": { - "node": ">=16" + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 6" } }, - "node_modules/get-proto": { - "version": "1.0.1", + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/workerpool": { + "version": "6.5.1", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/get-stream": { - "version": "4.1.0", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", "dev": true, "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/get-uri": { - "version": "6.0.4", + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/get-uri/node_modules/data-uri-to-buffer": { - "version": "6.0.2", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 14" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/git-repo-info": { - "version": "2.1.1", + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">= 4.0" + "node": ">=7.0.0" } }, - "node_modules/gitconfiglocal": { - "version": "2.1.0", + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "BSD", + "license": "MIT" + }, + "node_modules/wrap-ansi/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, + "license": "MIT", "dependencies": { - "ini": "^1.3.2" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/gitconfiglocal/node_modules/ini": { - "version": "1.3.8", + "node_modules/wrappy": { + "version": "1.0.2", "dev": true, "license": "ISC" }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "node_modules/ws": { + "version": "8.17.1", "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.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" + "license": "MIT", + "engines": { + "node": ">=10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, - "engines": { - "node": ">= 6" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/glob-stream": { - "version": "8.0.3", - "dev": true, + "node_modules/xtend": { + "version": "4.0.2", "license": "MIT", - "dependencies": { - "@gulpjs/to-absolute-glob": "^4.0.0", - "anymatch": "^3.1.3", - "fastq": "^1.13.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "is-negated-glob": "^1.0.0", - "normalize-path": "^3.0.0", - "streamx": "^2.12.5" - }, "engines": { - "node": ">=10.13.0" + "node": ">=0.4" } }, - "node_modules/glob-stream/node_modules/glob-parent": { - "version": "6.0.2", + "node_modules/y18n": { + "version": "5.0.8", "dev": true, "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, "engines": { - "node": ">=10.13.0" + "node": ">=10" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "node_modules/yallist": { + "version": "3.1.1", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "1.3.3", "dev": true, - "license": "BSD-2-Clause" + "license": "MIT/X11" }, - "node_modules/glob-watcher": { - "version": "6.0.0", + "node_modules/yargs-parser": { + "version": "21.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0", - "chokidar": "^3.5.3" - }, + "license": "ISC", "engines": { - "node": ">= 10.13.0" + "node": ">=12" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/yargs-unparser": { + "version": "2.0.0", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global": { - "version": "4.4.0", + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-modules": { - "version": "1.0.0", + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", "dev": true, "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/global-prefix": { - "version": "1.0.2", + "node_modules/yauzl": { + "version": "3.1.3", "dev": true, "license": "MIT", "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/global-prefix/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "node_modules/global-prefix/node_modules/isexe": { - "version": "2.0.0", + "node_modules/yocto-queue": { + "version": "1.0.0", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" + "license": "MIT", + "engines": { + "node": ">=18" }, - "bin": { - "which": "bin/which" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globals": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", - "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "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, "license": "MIT", "engines": { @@ -11771,9285 +22351,13911 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", + "node_modules/zip-stream": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, - "node_modules/glogg": { - "version": "2.2.0", + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.5.2", "dev": true, "license": "MIT", "dependencies": { - "sparkles": "^2.1.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 10.13.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/gopd": { - "version": "1.2.0", + "node_modules/zip-stream/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "dev": true, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/colinhacks" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "requires": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" + "@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "dev": true, - "license": "MIT" + "@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "requires": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "@babel/eslint-parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz", + "integrity": "sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA==", "dev": true, - "license": "MIT" + "requires": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + } }, - "node_modules/gulp": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-watcher": "^6.0.0", - "gulp-cli": "^3.1.0", - "undertaker": "^2.0.0", - "vinyl-fs": "^4.0.2" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">=10.13.0" + "@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "requires": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" } }, - "node_modules/gulp-babel": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", - "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", - "dependencies": { - "plugin-error": "^1.0.1", - "replace-ext": "^1.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "requires": { + "@babel/types": "^7.27.3" } }, - "node_modules/gulp-babel/node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "requires": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" } }, - "node_modules/gulp-babel/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "engines": { - "node": ">=0.10.0" + "@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" } }, - "node_modules/gulp-babel/node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "engines": { - "node": ">=0.10.0" + "@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" } }, - "node_modules/gulp-babel/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "requires": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" }, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "requires": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "requires": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "requires": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "requires": { + "@babel/types": "^7.27.1" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==" + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + } + }, + "@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "requires": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + } + }, + "@babel/helper-string-parser": { + "version": "7.27.1" + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/helper-validator-option": { + "version": "7.27.1" + }, + "@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "requires": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "requires": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" } }, - "node_modules/gulp-babel/node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" + "@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "requires": { + "@babel/types": "^7.28.6" } }, - "node_modules/gulp-babel/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "engines": { - "node": ">= 0.10" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" } }, - "node_modules/gulp-babel/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-clean": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fancy-log": "^1.3.2", - "plugin-error": "^0.1.2", - "rimraf": "^2.6.2", - "through2": "^2.0.3", - "vinyl": "^2.1.0" - }, - "engines": { - "node": ">=0.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-clean/node_modules/fancy-log": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" } }, - "node_modules/gulp-clean/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "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" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" } }, - "node_modules/gulp-clean/node_modules/plugin-error": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "requires": {} + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-clean/node_modules/rimraf": { - "version": "2.7.1", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-clean/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-cli": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@gulpjs/messages": "^1.1.0", - "chalk": "^4.1.2", - "copy-props": "^4.0.0", - "gulplog": "^2.2.0", - "interpret": "^3.1.1", - "liftoff": "^5.0.1", - "mute-stdout": "^2.0.0", - "replace-homedir": "^2.0.0", - "semver-greatest-satisfied-range": "^2.0.0", - "string-width": "^4.2.3", - "v8flags": "^4.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">=10.13.0" + "@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/gulp-cli/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-cli/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "@babel/plugin-transform-async-generator-functions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", + "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "requires": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" } }, - "node_modules/gulp-cli/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" + } }, - "node_modules/gulp-cli/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" + "@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" } }, - "node_modules/gulp-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" + "@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" } }, - "node_modules/gulp-concat": { - "version": "2.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" + "@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-concat/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-connect": { - "version": "5.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^2.0.5", - "connect": "^3.6.6", - "connect-livereload": "^0.6.0", - "fancy-log": "^1.3.2", - "map-stream": "^0.0.7", - "send": "^0.16.2", - "serve-index": "^1.9.1", - "serve-static": "^1.13.2", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", + "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-connect/node_modules/ansi-colors": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-connect/node_modules/fancy-log": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" + "@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" } }, - "node_modules/gulp-filter": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-9.0.1.tgz", - "integrity": "sha512-knVYL8h9bfYIeft3VokVTkuaWJkQJMrFCS3yVjZQC6BGg+1dZFoeUY++B9D2X4eFpeNTx9StWK0qnDby3NO3PA==", - "dev": true, - "dependencies": { - "multimatch": "^7.0.0", - "plugin-error": "^2.0.1", - "slash": "^5.1.0", - "streamfilter": "^3.0.0", - "to-absolute-glob": "^3.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "gulp": ">=4" - }, - "peerDependenciesMeta": { - "gulp": { - "optional": true - } + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-if": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "gulp-match": "^1.1.0", - "ternary-stream": "^3.0.0", - "through2": "^3.0.1" + "@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-if/node_modules/through2": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "@babel/plugin-transform-for-of": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, - "node_modules/gulp-js-escape": { - "version": "1.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "through2": "^0.6.3" + "@babel/plugin-transform-function-name": { + "version": "7.27.1", + "requires": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, - "node_modules/gulp-js-escape/node_modules/isarray": { - "version": "0.0.1", - "dev": true, - "license": "MIT" + "@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" + } }, - "node_modules/gulp-js-escape/node_modules/readable-stream": { - "version": "1.0.34", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "@babel/plugin-transform-literals": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-js-escape/node_modules/string_decoder": { - "version": "0.10.31", - "dev": true, - "license": "MIT" + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" + } }, - "node_modules/gulp-js-escape/node_modules/through2": { - "version": "0.6.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-match": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.3" + "@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "requires": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-rename": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.1.0.tgz", - "integrity": "sha512-dGuzuH8jQGqCMqC544IEPhs5+O2l+IkdoSZsgd4kY97M1CxQeI3qrmweQBIrxLBbjbe/8uEWK8HHcNBc3OCy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "requires": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-replace": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/vinyl": "^2.0.4", - "istextorbinary": "^3.0.0", - "replacestream": "^4.0.3", - "yargs-parser": ">=5.0.0-security.0" - }, - "engines": { - "node": ">=10" + "@babel/plugin-transform-modules-systemjs": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "requires": { + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" } }, - "node_modules/gulp-sourcemaps": { - "version": "3.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@gulp-sourcemaps/identity-map": "^2.0.1", - "@gulp-sourcemaps/map-sources": "^1.0.0", - "acorn": "^6.4.1", - "convert-source-map": "^1.0.0", - "css": "^3.0.0", - "debug-fabulous": "^1.0.0", - "detect-newline": "^2.0.0", - "graceful-fs": "^4.0.0", - "source-map": "^0.6.0", - "strip-bom-string": "^1.0.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">= 6" + "@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "requires": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-sourcemaps/node_modules/acorn": { - "version": "6.4.2", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-sourcemaps/node_modules/convert-source-map": { - "version": "1.9.0", - "dev": true, - "license": "MIT" + "@babel/plugin-transform-new-target": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" + } }, - "node_modules/gulp-sourcemaps/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-tap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-tap/-/gulp-tap-2.0.0.tgz", - "integrity": "sha512-U5/v1bTozx672QHzrvzPe6fPl2io7Wqyrx2y30AG53eMU/idH4BrY/b2yikOkdyhjDqGgPoMUMnpBg9e9LK8Nw==", - "dev": true, - "dependencies": { - "through2": "^3.0.1" + "@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-tap/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "requires": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" } }, - "node_modules/gulp-wrap": { - "version": "0.15.0", - "dependencies": { - "consolidate": "^0.15.1", - "es6-promise": "^4.2.6", - "fs-readfile-promise": "^3.0.1", - "js-yaml": "^3.13.0", - "lodash": "^4.17.11", - "node.extend": "2.0.2", - "plugin-error": "^1.0.1", - "through2": "^3.0.1", - "tryit": "^1.0.1", - "vinyl-bufferstream": "^1.0.1" - }, - "engines": { - "node": ">=6.14", - "npm": ">=1.4.3" + "@babel/plugin-transform-object-super": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" } }, - "node_modules/gulp-wrap/node_modules/ansi-colors": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-wrap/node_modules/arr-diff": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, - "node_modules/gulp-wrap/node_modules/arr-union": { - "version": "3.1.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulp-wrap/node_modules/extend-shallow": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" + "@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-wrap/node_modules/plugin-error": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" + "@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gulp-wrap/node_modules/through2": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/gulplog": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "glogg": "^2.2.0" - }, - "engines": { - "node": ">= 10.13.0" + "@babel/plugin-transform-regenerator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", + "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/gzip-size": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" } }, - "node_modules/handlebars": { - "version": "4.7.8", + "@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.27.4", "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "requires": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" } }, - "node_modules/has": { - "version": "1.0.4", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" + "@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/has-proto": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" } }, - "node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + } + }, + "@babel/preset-env": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "requires": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" }, - "engines": { - "node": ">= 0.4" + "dependencies": { + "babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + } + } } }, - "node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" } }, - "node_modules/headers-utils": { - "version": "1.2.5", + "@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + } + }, + "@babel/register": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", + "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", "dev": true, - "license": "MIT" + "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", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-locate": { + "version": "3.0.0", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "dev": true + }, + "pify": { + "version": "4.0.1", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.2", + "dev": true + } + } }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==" + }, + "@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "requires": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "requires": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" } }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.2.2", + "@browserstack/ai-sdk-node": { + "version": "1.5.17", "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" + "requires": { + "axios": "^1.7.4", + "uuid": "9.0.1" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" + "@chiragrupani/karma-chromium-edge-launcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@chiragrupani/karma-chromium-edge-launcher/-/karma-chromium-edge-launcher-2.4.1.tgz", + "integrity": "sha512-HwTlN4dk7dnL9m5nEonq7cI3Wa787wYfGVWeb4oWPMySIEhFpA7/BYQ8zMbpQ4YkSQxVnvY1502aWdbI3w7DeA==", + "dev": true }, - "node_modules/htmlfy": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.8.1.tgz", - "integrity": "sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==", - "dev": true, - "license": "MIT" + "@colors/colors": { + "version": "1.5.0", + "dev": true }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } + "@discoveryjs/json-ext": { + "version": "0.5.7", + "dev": true }, - "node_modules/http-errors": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" + "@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", + "@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" + "optional": true, + "requires": { + "tslib": "^2.4.0" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", + "@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" + "optional": true, + "requires": { + "tslib": "^2.4.0" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.1", + "@es-joy/jsdoccomment": { + "version": "0.49.0", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" + "requires": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", + "@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } + "optional": true }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } + "optional": true }, - "node_modules/iab-adcom": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/iab-adcom/-/iab-adcom-1.0.6.tgz", - "integrity": "sha512-XAJdidfrFgZNKmHqcXD3Zhqik2rdSmOs+PGgeVfPWgthxvzNBQxkZnKkW3QAau6mrLjtJc8yOQC6awcEv7gryA==", - "engines": { - "node": ">=14.0.0" - } + "@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "dev": true, + "optional": true }, - "node_modules/iab-native": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/iab-native/-/iab-native-1.0.0.tgz", - "integrity": "sha512-AxGYpKGRcyG5pbEAqj+ssxNwZAfxC0pRwyKc0MYoKjm0UeOoUNCWrZV0HGimcQii6ebe6MRqBQEeENyHM4qTdQ==", - "engines": { - "node": ">=14.0.0" - } + "@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "dev": true, + "optional": true }, - "node_modules/iab-openrtb": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iab-openrtb/-/iab-openrtb-1.0.1.tgz", - "integrity": "sha512-egawJx6+pMh/6uA/hak1y+R2+XCSH2jxteSkWlY98/XdQQftaMUMllUFNMKrHwq9lgCI70Me06g4JCCnV6E62g==", - "dependencies": { - "iab-adcom": "1.0.6" - }, - "engines": { - "node": ">=14.0.0" - } + "@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "dev": true, + "optional": true }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } + "@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "dev": true, + "optional": true }, - "node_modules/ieee754": { - "version": "1.2.1", + "@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "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" - } - ], - "license": "BSD-3-Clause" + "optional": true }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } + "optional": true }, - "node_modules/immediate": { - "version": "3.0.6", + "@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "dev": true, - "license": "MIT" + "optional": true }, - "node_modules/import-fresh": { - "version": "3.3.0", + "@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", + "@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } + "optional": true }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", + "@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true }, - "node_modules/imurmurhash": { - "version": "0.1.4", + "@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } + "optional": true }, - "node_modules/individual": { - "version": "2.0.0", - "dev": true + "@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "dev": true, + "optional": true }, - "node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } + "@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "dev": true, + "optional": true }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" + "@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "dev": true, + "optional": true }, - "node_modules/inquirer": { - "version": "12.9.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.0.tgz", - "integrity": "sha512-LlFVmvWVCun7uEgPB3vups9NzBrjJn48kRNtFGw3xU1H5UXExTEz/oF1JGLaB0fvlkUB+W6JfgLcSEaSdH7RPA==", + "@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/prompts": "^7.8.0", - "@inquirer/type": "^3.0.8", - "ansi-escapes": "^4.3.2", - "mute-stream": "^2.0.0", - "run-async": "^4.0.5", - "rxjs": "^7.8.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } + "optional": true }, - "node_modules/internal-slot": { - "version": "1.1.0", + "@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } + "optional": true }, - "node_modules/interpret": { - "version": "3.1.1", + "@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } + "optional": true }, - "node_modules/ip-address": { - "version": "9.0.5", + "@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } + "optional": true }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", + "@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "dev": true, - "license": "BSD-3-Clause" + "optional": true }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } + "@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "dev": true, + "optional": true }, - "node_modules/is": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": "*" - } + "@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "dev": true, + "optional": true }, - "node_modules/is-absolute": { - "version": "1.0.0", + "@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } + "optional": true }, - "node_modules/is-arguments": { - "version": "1.1.1", + "@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "optional": true }, - "node_modules/is-array-buffer": { - "version": "3.0.5", + "@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "eslint-visitor-keys": "^3.4.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "dev": true + } } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" + "@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true }, - "node_modules/is-async-function": { - "version": "2.1.1", + "@eslint/compat": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", + "integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "@eslint/core": "^0.17.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" + } + } } }, - "node_modules/is-bigint": { - "version": "1.1.0", + "@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" + "@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true + }, + "@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", + "@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "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 + }, + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } } }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "dev": true + }, + "@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true + }, + "@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" + "requires": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" } }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "@gulp-sourcemaps/identity-map": { + "version": "2.0.1", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "requires": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" }, - "engines": { - "node": ">=10" + "dependencies": { + "acorn": { + "version": "6.4.2", + "dev": true + }, + "through2": { + "version": "3.0.2", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, - "node_modules/is-callable": { - "version": "1.2.7", + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "through2": { + "version": "2.0.5", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "node_modules/is-core-module": { - "version": "2.15.1", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@gulpjs/messages": { + "version": "1.1.0", + "dev": true + }, + "@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "dev": true, + "requires": { + "is-negated-glob": "^1.0.0" } }, - "node_modules/is-data-view": { - "version": "1.0.2", + "@hapi/boom": { + "version": "9.1.4", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@hapi/hoek": "9.x.x" } }, - "node_modules/is-date-object": { - "version": "1.1.0", + "@hapi/cryptiles": { + "version": "5.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@hapi/boom": "9.x.x" } }, - "node_modules/is-docker": { - "version": "2.2.1", + "@hapi/hoek": { + "version": "9.3.0", + "dev": true + }, + "@humanfs/core": { + "version": "0.19.1", + "dev": true + }, + "@humanfs/node": { + "version": "0.16.6", "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" + "requires": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true + } } }, - "node_modules/is-extendable": { + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "dev": true + }, + "@humanwhocodes/module-importer": { "version": "1.0.1", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, - "node_modules/is-extendable/node_modules/is-plain-object": { - "version": "2.0.4", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "@humanwhocodes/retry": { + "version": "0.4.2", + "dev": true + }, + "@inquirer/checkbox": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", + "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", + "dev": true, + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", + "@inquirer/core": { + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "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 + } } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" + "@inquirer/editor": { + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.16.tgz", + "integrity": "sha512-iSzLjT4C6YKp2DU0fr8T7a97FnRRxMO6CushJnW5ktxLNM2iNeuyUuUA5255eOLPORoGYCrVnuDOEBdGkHGkpw==", + "dev": true, + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/external-editor": "^1.0.0", + "@inquirer/type": "^3.0.8" } }, - "node_modules/is-function": { - "version": "1.0.2", + "@inquirer/expand": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", + "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", "dev": true, - "license": "MIT" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + } }, - "node_modules/is-generator-function": { - "version": "1.0.10", + "@inquirer/external-editor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.0.tgz", + "integrity": "sha512-5v3YXc5ZMfL6OJqXPrX9csb4l7NlQA2doO1yynUjpUChT9hg4JcuBVP0RbsEJ/3SL/sxWEyFjT2W69ZhtoBWqg==", "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "chardet": "^2.1.0", + "iconv-lite": "^0.6.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "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" + } + } } }, - "node_modules/is-map": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true }, - "node_modules/is-nan": { - "version": "1.3.2", + "@inquirer/input": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", + "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" } }, - "node_modules/is-negated-glob": { - "version": "1.0.0", + "@inquirer/number": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", + "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "license": "MIT", - "engines": { - "node": ">=0.12.0" + "@inquirer/password": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", + "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", + "dev": true, + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2" } }, - "node_modules/is-number-object": { - "version": "1.1.1", + "@inquirer/prompts": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.0.tgz", + "integrity": "sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@inquirer/checkbox": "^4.2.0", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.15", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", + "@inquirer/rawlist": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", + "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", + "@inquirer/search": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", + "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" } }, - "node_modules/is-promise": { - "version": "2.2.2", + "@inquirer/select": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", + "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", "dev": true, - "license": "MIT" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } }, - "node_modules/is-regex": { - "version": "1.2.1", + "@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "requires": {} + }, + "@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } } }, - "node_modules/is-relative": { - "version": "1.0.0", + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" } }, - "node_modules/is-running": { - "version": "2.1.0", + "@istanbuljs/schema": { + "version": "0.1.3", + "dev": true + }, + "@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, - "license": "BSD" + "peer": true }, - "node_modules/is-set": { - "version": "2.0.3", + "@jest/expect-utils": { + "version": "29.7.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "jest-get-type": "^29.6.3" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", + "@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "peer": true }, - "node_modules/is-stream": { - "version": "1.1.0", + "@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "peer": true, + "requires": { + "@types/node": "*", + "jest-regex-util": "30.0.1" } }, - "node_modules/is-string": { - "version": "1.1.1", + "@jest/schemas": { + "version": "29.6.3", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@sinclair/typebox": "^0.27.8" } }, - "node_modules/is-symbol": { - "version": "1.1.1", + "@jest/types": { + "version": "29.6.3", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/is-typed-array": { - "version": "1.1.15", + "@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2" + }, + "@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.0" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/is-unc-path": { - "version": "1.0.0", + "@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" + "optional": true, + "requires": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "eslint-scope": "5.1.1" } }, - "node_modules/is-valid-glob": { - "version": "1.0.0", + "@nodelib/fs.scandir": { + "version": "2.1.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", + "@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } }, - "node_modules/is-weakref": { - "version": "1.1.1", + "@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true + }, + "@open-draft/until": { + "version": "1.0.3", + "dev": true + }, + "@percy/appium-app": { + "version": "2.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@percy/sdk-utils": "^1.30.9", + "tmp": "^0.2.3" } }, - "node_modules/is-weakset": { - "version": "2.0.3", + "@percy/sdk-utils": { + "version": "1.31.0", + "dev": true + }, + "@percy/selenium-webdriver": { + "version": "2.2.3", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "@percy/sdk-utils": "^1.30.9", + "node-request-interceptor": "^0.6.3" } }, - "node_modules/is-windows": { - "version": "1.0.2", + "@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "optional": true }, - "node_modules/is-wsl": { - "version": "2.2.0", + "@pkgr/core": { + "version": "0.1.1", + "dev": true + }, + "@polka/url": { + "version": "1.0.0-next.25", + "dev": true + }, + "@promptbook/utils": { + "version": "0.50.0-10", "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "moment": "2.30.1", + "prettier": "2.8.1", + "spacetrim": "0.11.25" } }, - "node_modules/isarray": { - "version": "2.0.5", + "@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", "dev": true, - "license": "MIT" - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "license": "MIT", - "engines": { - "node": ">= 8.0.0" + "requires": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" + "dependencies": { + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "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" + } + } } }, - "node_modules/isexe": { - "version": "3.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16" - } + "@rtsao/scc": { + "version": "1.1.0", + "dev": true }, - "node_modules/isobject": { - "version": "3.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "@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/istanbul": { - "version": "0.4.5", + "@sinclair/typebox": { + "version": "0.27.8", + "dev": true + }, + "@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": { + "version": "3.0.1", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "istanbul": "lib/cli.js" + "requires": { + "type-detect": "4.0.8" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", + "@sinonjs/fake-timers": { + "version": "13.0.5", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" + "requires": { + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", + "@sinonjs/samsam": { + "version": "8.0.2", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "requires": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" }, - "engines": { - "node": ">=8" + "dependencies": { + "type-detect": { + "version": "4.1.0", + "dev": true + } } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", + "@sinonjs/text-encoding": { + "version": "0.7.3", + "dev": true + }, + "@socket.io/component-emitter": { + "version": "3.1.2", + "dev": true + }, + "@stylistic/eslint-plugin": { + "version": "2.11.0", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "requires": { + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" }, - "engines": { - "node": ">=10" + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.0", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "dev": true + }, + "picomatch": { + "version": "4.0.2", + "dev": true + } } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", + "@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "dev": true + }, + "@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "optional": true, + "requires": { + "tslib": "^2.4.0" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", + "@types/cookie": { + "version": "0.4.1", + "dev": true + }, + "@types/cors": { + "version": "2.8.17", "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "@types/node": "*" } }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.6.2", + "@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", + "@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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "@types/expect": { + "version": "1.20.4", + "dev": true + }, + "@types/gitconfiglocal": { + "version": "2.0.3", + "dev": true + }, + "@types/google-publisher-tag": { + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@types/google-publisher-tag/-/google-publisher-tag-1.20250428.0.tgz", + "integrity": "sha512-W+aTMsM4e8PE/TkH/RkMbmmwEFg2si9eUugS5/lt88wkEClqcALi+3WLXW39Xgzu89+3igi/RNIpPLKdt6W7Dg==", + "dev": true + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.3", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" + "requires": { + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/istanbul-reports": { - "version": "3.1.7", + "@types/istanbul-reports": { + "version": "3.0.4", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@types/istanbul-lib-report": "*" } }, - "node_modules/istanbul/node_modules/glob": { - "version": "5.0.15", + "@types/json-schema": { + "version": "7.0.15" + }, + "@types/json5": { + "version": "0.0.29", + "dev": true + }, + "@types/mocha": { + "version": "10.0.6", + "dev": true + }, + "@types/node": { + "version": "20.14.2", "dev": true, - "license": "ISC", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" + "requires": { + "undici-types": "~5.26.4" } }, - "node_modules/istanbul/node_modules/has-flag": { - "version": "1.0.0", + "@types/normalize-package-data": { + "version": "2.4.4", + "dev": true + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.5", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.3", + "dev": true + }, + "@types/triple-beam": { + "version": "1.3.5", + "dev": true + }, + "@types/vinyl": { + "version": "2.0.12", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "@types/expect": "^1.20.4", + "@types/node": "*" } }, - "node_modules/istanbul/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" + "@types/which": { + "version": "2.0.2", + "dev": true }, - "node_modules/istanbul/node_modules/resolve": { - "version": "1.1.7", + "@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, - "license": "MIT" + "requires": { + "@types/node": "*" + } }, - "node_modules/istanbul/node_modules/supports-color": { - "version": "3.2.3", + "@types/yargs": { + "version": "17.0.33", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^1.0.0" - }, - "engines": { - "node": ">=0.8.0" + "requires": { + "@types/yargs-parser": "*" } }, - "node_modules/istanbul/node_modules/which": { - "version": "1.3.1", + "@types/yargs-parser": { + "version": "21.0.3", + "dev": true + }, + "@types/yauzl": { + "version": "2.10.3", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "optional": true, + "requires": { + "@types/node": "*" } }, - "node_modules/istextorbinary": { - "version": "3.3.0", + "@typescript-eslint/eslint-plugin": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", + "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", "dev": true, - "license": "MIT", - "dependencies": { - "binaryextensions": "^2.2.0", - "textextensions": "^3.2.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/type-utils": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" }, - "funding": { - "url": "https://bevry.me/fund" + "dependencies": { + "ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true + } } }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "@typescript-eslint/parser": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", + "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4" } }, - "node_modules/jackspeak": { - "version": "3.4.3", + "@typescript-eslint/project-service": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.39.0", + "@typescript-eslint/types": "^8.39.0", + "debug": "^4.3.4" } }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "@typescript-eslint/scope-manager": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" + "requires": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0" } }, - "node_modules/jake/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "@typescript-eslint/tsconfig-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", "dev": true, - "license": "MIT" + "requires": {} }, - "node_modules/jest-diff": { - "version": "29.7.0", + "@typescript-eslint/type-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", + "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", + "@typescript-eslint/types": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "@typescript-eslint/project-service": "8.39.0", + "@typescript-eslint/tsconfig-utils": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + } } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", + "@typescript-eslint/utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", + "@typescript-eslint/visitor-keys": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "requires": { + "@typescript-eslint/types": "8.39.0", + "eslint-visitor-keys": "^4.2.1" }, - "engines": { - "node": ">=7.0.0" + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + } } }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", + "@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", "dev": true, - "license": "MIT" + "optional": true }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", + "@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", + "@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/jest-get-type": { - "version": "29.6.3", + "@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "optional": true }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", + "@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "optional": true }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "optional": true }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", + "@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "optional": true }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", + "@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } + "optional": true }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", + "@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", "dev": true, - "license": "MIT" + "optional": true }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", + "@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", + "@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/jest-message-util": { - "version": "29.7.0", + "@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "optional": true }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", + "@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "optional": true + }, + "@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "dev": true, + "optional": true }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", + "@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "optional": true }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", + "@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "optional": true, + "requires": { + "@napi-rs/wasm-runtime": "^0.2.11" } }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", + "@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", "dev": true, - "license": "MIT" + "optional": true }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", + "@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", + "@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", + "@videojs/http-streaming": { + "version": "2.16.3", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "3.0.5", + "aes-decrypter": "3.1.3", + "global": "^4.4.0", + "m3u8-parser": "4.8.0", + "mpd-parser": "^0.22.1", + "mux.js": "6.0.1", + "video.js": "^6 || ^7" } }, - "node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "@videojs/vhs-utils": { + "version": "3.0.5", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "jest-util": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "requires": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" } }, - "node_modules/jest-mock/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "@videojs/xhr": { + "version": "2.6.0", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "requires": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "@vitest/pretty-format": { + "version": "2.1.9", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "requires": { + "tinyrainbow": "^1.2.0" } }, - "node_modules/jest-mock/node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "@vitest/snapshot": { + "version": "2.1.9", "dev": true, - "license": "MIT", - "peer": true + "requires": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + } }, - "node_modules/jest-mock/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==", + "@wdio/browserstack-service": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.19.1.tgz", + "integrity": "sha512-9goHyn4PckZXp1GlYgTGslCFd+2BJPcrRjzWD8ziXJ7WWIC8/94+Y3Sp3HTnlZayrdSIjTXtHKZODP1qugVcWg==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "@browserstack/ai-sdk-node": "1.5.17", + "@percy/appium-app": "^2.0.9", + "@percy/selenium-webdriver": "^2.2.2", + "@types/gitconfiglocal": "^2.0.1", + "@wdio/logger": "9.18.0", + "@wdio/reporter": "9.19.1", + "@wdio/types": "9.19.1", + "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", + "undici": "^6.21.3", + "uuid": "^11.1.0", + "webdriverio": "9.19.1", + "winston-transport": "^4.5.0", + "yauzl": "^3.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/reporter": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.19.1.tgz", + "integrity": "sha512-nv5TZg+rUUlC3NGNDP2DGzpd2grU/3D27M9MsPV37TjGLccEVZYlbu2BnK3Y9mSqXWt7CZuFC4GXBF+5vQG6gw==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "diff": "^8.0.2", + "object-inspect": "^1.12.0" + } + }, + "@wdio/types": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true + }, + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true + }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true + } } }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@wdio/cli": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.19.1.tgz", + "integrity": "sha512-1JDvutIp1mYk2f3KaBdcHAMw9UQlAMFnXbB4byuOMgik75HIgF+mrsasHj8wzfJTm9BbLwQ2h/6yGLHPTXvc0g==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "requires": { + "@vitest/snapshot": "^2.1.1", + "@wdio/config": "9.19.1", + "@wdio/globals": "9.17.0", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "async-exit-hook": "^2.0.1", + "chalk": "^5.4.1", + "chokidar": "^4.0.0", + "create-wdio": "9.18.2", + "dotenv": "^17.2.0", + "import-meta-resolve": "^4.0.0", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "read-pkg-up": "^10.0.0", + "tsx": "^4.7.2", + "webdriverio": "9.19.1", + "yargs": "^17.7.2" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "dependencies": { + "@jest/expect-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "dev": true, + "peer": true, + "requires": { + "@jest/get-type": "30.0.1" + } + }, + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "peer": true, + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": 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, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "dev": true, + "peer": true + }, + "@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "peer": true, + "requires": { + "tinyrainbow": "^2.0.0" + } + }, + "@wdio/config": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", + "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", + "dev": true, + "requires": { + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + } + }, + "@wdio/globals": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.17.0.tgz", + "integrity": "sha512-i38o7wlipLllNrk2hzdDfAmk6nrqm3lR2MtAgWgtHbwznZAKkB84KpkNFfmUXw5Kg3iP1zKlSjwZpKqenuLc+Q==", + "dev": true, + "requires": {} + }, + "@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "dev": true + }, + "@wdio/types": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", + "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "peer": true + }, + "chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "dev": true + }, + "chokidar": { + "version": "4.0.3", + "dev": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "peer": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": 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, + "peer": true + }, + "expect": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + } + }, + "expect-webdriverio": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.4.1.tgz", + "integrity": "sha512-jH4qhahRNGPWbCCVcCpLDl/kvFJ1eOzVnrd1K/sG1RhKr6bZsgZQUiOE3bafVqSOfKP+ay8bM/VagP4+XsO9Xw==", + "dev": true, + "peer": true, + "requires": { + "@vitest/snapshot": "^3.2.4", + "expect": "^30.0.0", + "jest-matcher-utils": "^30.0.0", + "lodash.isequal": "^4.5.0" + }, + "dependencies": { + "@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "peer": true, + "requires": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + } + } + } + }, + "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, + "peer": true + }, + "jest-diff": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "dev": true, + "peer": true, + "requires": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "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, + "peer": 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, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-matcher-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" + }, + "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, + "peer": 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, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-message-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": 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, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": 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, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "peer": true + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "peer": true + }, + "pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "peer": true, + "requires": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + } + }, + "readdirp": { + "version": "4.1.2", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "peer": true + }, + "yargs": { + "version": "17.7.2", + "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" + } + } } }, - "node_modules/jest-mock/node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "@wdio/concise-reporter": { + "version": "8.38.2", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" + "requires": { + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", + "chalk": "^5.0.1", + "pretty-ms": "^7.0.1" + }, + "dependencies": { + "chalk": { + "version": "5.3.0", + "dev": true } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" } }, - "node_modules/jest-mock/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==", + "@wdio/config": { + "version": "9.15.0", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" + "requires": { + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, - "engines": { - "node": ">=7.0.0" + "dependencies": { + "@wdio/logger": { + "version": "9.15.0", + "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.15.0", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.15.0", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true + } } }, - "node_modules/jest-mock/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==", + "@wdio/dot-reporter": { + "version": "9.15.0", "dev": true, - "license": "MIT", - "peer": true + "requires": { + "@wdio/reporter": "9.15.0", + "@wdio/types": "9.15.0", + "chalk": "^5.0.1" + }, + "dependencies": { + "@wdio/logger": { + "version": "9.15.0", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/reporter": { + "version": "9.15.0", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "diff": "^7.0.0", + "object-inspect": "^1.12.0" + } + }, + "@wdio/types": { + "version": "9.15.0", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true + }, + "diff": { + "version": "7.0.0", + "dev": true + } + } }, - "node_modules/jest-mock/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==", + "@wdio/globals": { + "version": "9.15.0", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" + "requires": { + "expect-webdriverio": "^5.1.0", + "webdriverio": "9.15.0" + }, + "dependencies": { + "@vitest/pretty-format": { + "version": "3.2.3", + "dev": true, + "optional": true, + "requires": { + "tinyrainbow": "^2.0.0" + } + }, + "@vitest/snapshot": { + "version": "3.2.3", + "dev": true, + "optional": true, + "requires": { + "@vitest/pretty-format": "3.2.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + } + }, + "@wdio/logger": { + "version": "9.15.0", + "dev": true, + "optional": 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.15.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.15.0.tgz", + "integrity": "sha512-hR0Dm9TsrjtgOLWOjUMYTOB1hWIlnDzFgZt7XGOzI9Ig8Qa+TDfZSFaZukGxqLIZS/eGhxpnunSHaTAXwJIxYA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.15.0.tgz", + "integrity": "sha512-XuT1PE1nh4wwJfQW6IN4UT6+iv0+Yf4zhgMh5et04OX6tfrIXkWdx2SDimghDtRukp9i85DvIGWjdPEoQFQdaA==", + "dev": true, + "optional": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true, + "optional": true + }, + "expect-webdriverio": { + "version": "5.3.0", + "dev": true, + "optional": true, + "requires": { + "@vitest/snapshot": "^3.2.1", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + } + }, + "htmlfy": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", + "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", + "dev": true, + "optional": true + }, + "pathe": { + "version": "2.0.3", + "dev": true, + "optional": true + }, + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "optional": true, + "requires": { + "type-fest": "^2.12.2" + } + }, + "tinyrainbow": { + "version": "2.0.0", + "dev": true, + "optional": true + }, + "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 + }, + "webdriver": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.15.0.tgz", + "integrity": "sha512-JCW5xvhZtL6kjbckdePgVYMOlvWbh22F1VFkIf9pw3prwXI2EHED5Eq/nfDnNfHiqr0AfFKWmIDPziSafrVv4Q==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "undici": "^6.20.1", + "ws": "^8.8.0" + } + }, + "webdriverio": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.15.0.tgz", + "integrity": "sha512-910g6ktwXdAKGyhgCPGw9BzIKOEBBYMFN1bLwC3bW/3mFlxGHO/n70c7Sg9hrsu9VWTzv6m+1Clf27B9uz4a/Q==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/repl": "9.4.4", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "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.6.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "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.15.0" + } + } } }, - "node_modules/jest-mock/node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "@wdio/local-runner": { + "version": "9.15.0", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "requires": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.15.0", + "@wdio/repl": "9.4.4", + "@wdio/runner": "9.15.0", + "@wdio/types": "9.15.0", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "dependencies": { + "@wdio/logger": { + "version": "9.15.0", + "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.15.0", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true + } } }, - "node_modules/jest-mock/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "@wdio/logger": { + "version": "8.38.0", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "chalk": { + "version": "5.3.0", + "dev": true + } } }, - "node_modules/jest-mock/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==", + "@wdio/mocha-framework": { + "version": "9.12.6", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "requires": { + "@types/mocha": "^10.0.6", + "@types/node": "^20.11.28", + "@wdio/logger": "9.4.4", + "@wdio/types": "9.12.6", + "@wdio/utils": "9.12.6", + "mocha": "^10.3.0" }, - "engines": { - "node": ">=8" + "dependencies": { + "@wdio/logger": { + "version": "9.4.4", + "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.12.6", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true + } } }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "@wdio/protocols": { + "version": "9.15.0", + "dev": true + }, + "@wdio/repl": { + "version": "9.4.4", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "requires": { + "@types/node": "^20.1.0" } }, - "node_modules/jest-util": { - "version": "29.7.0", + "@wdio/reporter": { + "version": "8.38.2", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@types/node": "^20.1.0", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", + "@wdio/runner": { + "version": "9.15.0", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" + "requires": { + "@types/node": "^20.11.28", + "@wdio/config": "9.15.0", + "@wdio/dot-reporter": "9.15.0", + "@wdio/globals": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.1.0", + "webdriver": "9.15.0", + "webdriverio": "9.15.0" }, - "engines": { - "node": ">=8" + "dependencies": { + "@vitest/pretty-format": { + "version": "3.2.3", + "dev": true, + "requires": { + "tinyrainbow": "^2.0.0" + } + }, + "@vitest/snapshot": { + "version": "3.2.3", + "dev": true, + "requires": { + "@vitest/pretty-format": "3.2.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + } + }, + "@wdio/logger": { + "version": "9.15.0", + "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.15.0", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.15.0", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.15.0", + "@wdio/types": "9.15.0", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true + }, + "expect-webdriverio": { + "version": "5.3.0", + "dev": true, + "requires": { + "@vitest/snapshot": "^3.2.1", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + } + }, + "htmlfy": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", + "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", + "dev": true + }, + "pathe": { + "version": "2.0.3", + "dev": true + }, + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "requires": { + "type-fest": "^2.12.2" + } + }, + "tinyrainbow": { + "version": "2.0.0", + "dev": true + }, + "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 + }, + "webdriver": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.15.0.tgz", + "integrity": "sha512-JCW5xvhZtL6kjbckdePgVYMOlvWbh22F1VFkIf9pw3prwXI2EHED5Eq/nfDnNfHiqr0AfFKWmIDPziSafrVv4Q==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "deepmerge-ts": "^7.0.3", + "undici": "^6.20.1", + "ws": "^8.8.0" + } + }, + "webdriverio": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.15.0.tgz", + "integrity": "sha512-910g6ktwXdAKGyhgCPGw9BzIKOEBBYMFN1bLwC3bW/3mFlxGHO/n70c7Sg9hrsu9VWTzv6m+1Clf27B9uz4a/Q==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.15.0", + "@wdio/logger": "9.15.0", + "@wdio/protocols": "9.15.0", + "@wdio/repl": "9.4.4", + "@wdio/types": "9.15.0", + "@wdio/utils": "9.15.0", + "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.6.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "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.15.0" + } + } + } + }, + "@wdio/spec-reporter": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.43.0.tgz", + "integrity": "sha512-Qy5LsGrGHbXJdj2PveNV7CD5g1XpLPvd7wH8bAd9OtuZRTPknyZqYSvB8TlZIV/SfiNlWEJ/05mI/FcixNJ6Xg==", + "dev": true, + "requires": { + "@wdio/reporter": "8.43.0", + "@wdio/types": "8.41.0", + "chalk": "^5.1.2", + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "@types/node": { + "version": "22.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", + "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", + "dev": true, + "requires": { + "undici-types": "~6.21.0" + } + }, + "@wdio/reporter": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.43.0.tgz", + "integrity": "sha512-0ph8SabdMrgDmuLUEwA7yvkrvlCw4/EXttb3CGucjSkuiSbZNLhdTMXpyPoewh2soa253fIpnx79HztOsOzn5Q==", + "dev": true, + "requires": { + "@types/node": "^22.2.0", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.41.0", + "diff": "^7.0.0", + "object-inspect": "^1.12.0" + } + }, + "@wdio/types": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.41.0.tgz", + "integrity": "sha512-t4NaNTvJZci3Xv/yUZPH4eTL0hxrVTf5wdwNnYIBrzMnlRDbNefjQ0P7FM7ZjQCLaH92AEH6t/XanUId7Webug==", + "dev": true, + "requires": { + "@types/node": "^22.2.0" + } + }, + "chalk": { + "version": "5.3.0", + "dev": true + }, + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true + }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + } } }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", + "@wdio/types": { + "version": "8.38.2", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "@types/node": "^20.1.0" } }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", + "@wdio/utils": { + "version": "9.12.6", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.4.4", + "@wdio/types": "9.12.6", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.1", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, - "engines": { - "node": ">=7.0.0" + "dependencies": { + "@wdio/logger": { + "version": "9.4.4", + "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.12.6", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "chalk": { + "version": "5.4.1", + "dev": true + } } }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", + "@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "node_modules/jest-worker/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==", + "@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "requires": { + "@xtuc/long": "4.2.2" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "license": "MIT" + "@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, - "node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" + "@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/jsbn": { - "version": "1.1.0", + "@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, - "license": "MIT" + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.1.0", + "@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" + "@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" + "@xmldom/xmldom": { + "version": "0.8.10", + "dev": true }, - "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", + "@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 + }, + "@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 + }, + "@zip.js/zip.js": { + "version": "2.7.60", + "dev": true + }, + "abbrev": { + "version": "1.0.9", + "dev": true + }, + "abort-controller": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.8", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", + "acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true + }, + "acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, - "license": "MIT" + "requires": {} }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", + "acorn-jsx": { + "version": "5.3.2", "dev": true, - "license": "MIT" + "requires": {} }, - "node_modules/json5": { - "version": "2.2.3", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } + "acorn-walk": { + "version": "8.3.2", + "dev": true }, - "node_modules/jsonfile": { - "version": "6.1.0", + "aes-decrypter": { + "version": "3.1.3", "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0", + "pkcs7": "^1.0.4" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "agent-base": { + "version": "6.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" + "requires": { + "debug": "4" } }, - "node_modules/jszip": { - "version": "3.10.1", + "ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + }, "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } } }, - "node_modules/just-extend": { - "version": "6.2.0", + "ajv-keywords": { + "version": "3.5.2", "dev": true, - "license": "MIT" + "requires": {} }, - "node_modules/karma": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", - "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", - "license": "MIT", - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.7.2", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } + "amdefine": { + "version": "1.0.1", + "dev": true, + "optional": true }, - "node_modules/karma-babel-preprocessor": { - "version": "8.0.2", + "ansi-colors": { + "version": "4.1.3", + "dev": true + }, + "ansi-cyan": { + "version": "0.1.1", "dev": true, - "license": "ISC", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "@babel/core": "7" + "requires": { + "ansi-wrap": "0.1.0" } }, - "node_modules/karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", + "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, - "license": "MIT", - "dependencies": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" + "requires": { + "type-fest": "^0.21.3" }, - "peerDependencies": { - "karma": ">=0.9" + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } } }, - "node_modules/karma-chai": { - "version": "0.1.0", + "ansi-gray": { + "version": "0.1.1", "dev": true, - "license": "MIT", - "peerDependencies": { - "chai": "*", - "karma": ">=0.10.9" + "requires": { + "ansi-wrap": "0.1.0" } }, - "node_modules/karma-chrome-launcher": { - "version": "3.2.0", + "ansi-red": { + "version": "0.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "which": "^1.2.1" + "requires": { + "ansi-wrap": "0.1.0" } }, - "node_modules/karma-chrome-launcher/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" + "ansi-regex": { + "version": "5.0.1", + "dev": true }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", + "ansi-styles": { + "version": "3.2.1", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "requires": { + "color-convert": "^1.9.0" } }, - "node_modules/karma-coverage": { - "version": "2.2.1", + "ansi-wrap": { + "version": "0.1.0" + }, + "anymatch": { + "version": "3.1.3", "dev": true, - "license": "MIT", - "dependencies": { - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.1", - "istanbul-reports": "^3.0.5", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.0.0" + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "node_modules/karma-coverage-istanbul-reporter": { - "version": "3.0.3", + "archiver": { + "version": "7.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^3.0.2", - "minimatch": "^3.0.4" + "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" }, - "funding": { - "url": "https://github.com/sponsors/mattlewis92" + "dependencies": { + "async": { + "version": "3.2.6", + "dev": true + }, + "buffer": { + "version": "6.0.3", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "dev": true + }, + "readable-stream": { + "version": "4.5.2", + "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", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/glob": { - "version": "7.2.3", + "archiver-utils": { + "version": "5.0.2", "dev": true, - "license": "ISC", - "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": "*" + "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" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "dependencies": { + "buffer": { + "version": "6.0.3", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "is-stream": { + "version": "2.0.1", + "dev": true + }, + "readable-stream": { + "version": "4.5.2", + "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", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "are-docs-informative": { + "version": "0.0.2", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "requires": { + "sprintf-js": "~1.0.2" } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps": { - "version": "3.0.6", + "aria-query": { + "version": "5.3.0", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6" + "requires": { + "dequal": "^2.0.3" } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", + "arr-diff": { + "version": "1.1.0", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6" + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "dependencies": { + "array-slice": { + "version": "0.2.3", + "dev": true + } } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/make-dir": { + "arr-flatten": { + "version": "1.1.0", + "dev": true + }, + "arr-union": { "version": "2.1.0", + "dev": true + }, + "array-buffer-byte-length": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" + "requires": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/pify": { - "version": "4.0.1", + "array-differ": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", + "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "dev": true + }, + "array-flatten": { + "version": "1.1.1" + }, + "array-includes": { + "version": "3.1.8", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "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" } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/rimraf": { - "version": "2.7.1", + "array-slice": { + "version": "1.1.0", + "dev": true + }, + "array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true + }, + "array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" } }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/semver": { - "version": "5.7.2", + "array.prototype.findlastindex": { + "version": "1.2.5", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" } }, - "node_modules/karma-firefox-launcher": { - "version": "2.1.3", + "array.prototype.flat": { + "version": "1.3.2", "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^2.2.0", - "which": "^3.0.0" + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" } }, - "node_modules/karma-firefox-launcher/node_modules/isexe": { - "version": "2.0.0", + "array.prototype.flatmap": { + "version": "1.3.3", "dev": true, - "license": "ISC" + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + } }, - "node_modules/karma-firefox-launcher/node_modules/which": { - "version": "3.0.1", + "array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" } }, - "node_modules/karma-mocha": { - "version": "2.0.1", + "arraybuffer.prototype.slice": { + "version": "1.0.4", "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.3" + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" } }, - "node_modules/karma-mocha-reporter": { - "version": "2.2.5", + "assert": { + "version": "2.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" - }, - "peerDependencies": { - "karma": ">=0.13" + "requires": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, - "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { - "version": "3.0.1", + "assertion-error": { + "version": "1.1.0", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0" + }, + "ast-types": { + "version": "0.13.4", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "requires": { + "tslib": "^2.0.1" } }, - "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { - "version": "4.0.0", + "async": { + "version": "1.5.2", + "dev": true + }, + "async-done": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" + "requires": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" } }, - "node_modules/karma-opera-launcher": { + "async-exit-hook": { + "version": "2.0.1", + "dev": true + }, + "async-function": { "version": "1.0.0", + "dev": true + }, + "async-settle": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "peerDependencies": { - "karma": ">=0.9" + "requires": { + "async-done": "^2.0.0" } }, - "node_modules/karma-safari-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", - "integrity": "sha512-qmypLWd6F2qrDJfAETvXDfxHvKDk+nyIjpH9xIeI3/hENr0U3nuqkxaftq73PfXZ4aOuOChA6SnLW4m4AxfRjQ==", + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.7", "dev": true, - "peerDependencies": { - "karma": ">=0.9" + "requires": { + "possible-typed-array-names": "^1.0.0" } }, - "node_modules/karma-safarinative-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-safarinative-launcher/-/karma-safarinative-launcher-1.1.0.tgz", - "integrity": "sha512-vdMjdQDHkSUbOZc8Zq2K5bBC0yJGFEgfrKRJTqt0Um0SC1Rt8drS2wcN6UA3h4LgsL3f1pMcmRSvKucbJE8Qdg==", - "peerDependencies": { - "karma": ">=0.9" + "axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" } }, - "node_modules/karma-script-launcher": { - "version": "1.0.0", + "b4a": { + "version": "1.6.6", + "dev": true + }, + "babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", "dev": true, - "license": "MIT", - "peerDependencies": { - "karma": ">=0.9" + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "requires": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" } }, - "node_modules/karma-sinon": { - "version": "1.0.5", + "babel-plugin-polyfill-corejs3": { + "version": "0.11.1", "dev": true, - "engines": { - "node": ">= 0.10.0" - }, - "peerDependencies": { - "karma": ">=0.10", - "sinon": "*" + "requires": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" } }, - "node_modules/karma-sourcemap-loader": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz", - "integrity": "sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.10" + "babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.6.5" } }, - "node_modules/karma-spec-reporter": { - "version": "0.0.32", + "bach": { + "version": "2.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "colors": "^1.1.2" - }, - "peerDependencies": { - "karma": ">=0.9" + "requires": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" } }, - "node_modules/karma-webpack": { - "version": "5.0.1", + "balanced-match": { + "version": "1.0.2", + "dev": true + }, + "bare-events": { + "version": "2.5.4", "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^7.1.3", - "minimatch": "^9.0.3", - "webpack-merge": "^4.1.5" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } + "optional": true }, - "node_modules/karma-webpack/node_modules/glob": { - "version": "7.2.3", + "bare-fs": { + "version": "4.1.2", "dev": true, - "license": "ISC", - "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" + "optional": true, + "requires": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" } }, - "node_modules/karma-webpack/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", + "bare-os": { + "version": "3.6.1", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } + "optional": true }, - "node_modules/karma-webpack/node_modules/minimatch": { - "version": "9.0.4", + "bare-path": { + "version": "3.0.0", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "optional": true, + "requires": { + "bare-os": "^3.0.1" } }, - "node_modules/karma-webpack/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "bare-stream": { + "version": "2.6.5", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "optional": true, + "requires": { + "streamx": "^2.21.0" } }, - "node_modules/karma/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "base64-js": { + "version": "1.5.1", + "dev": true }, - "node_modules/karma/node_modules/cliui": { - "version": "7.0.4", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } + "base64id": { + "version": "2.0.0", + "dev": true }, - "node_modules/karma/node_modules/color-convert": { + "baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==" + }, + "basic-auth": { "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "dev": true, + "requires": { + "safe-buffer": "5.1.2" }, - "engines": { - "node": ">=7.0.0" + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "dev": true + } } }, - "node_modules/karma/node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" + "basic-ftp": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", + "dev": true }, - "node_modules/karma/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", - "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": "*" + "batch": { + "version": "0.6.1", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "dev": true + }, + "binary-extensions": { + "version": "2.3.0", + "dev": true + }, + "binaryextensions": { + "version": "2.3.0", + "dev": true + }, + "bl": { + "version": "5.1.0", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "dependencies": { + "buffer": { + "version": "6.0.3", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, - "node_modules/karma/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "bluebird": { + "version": "3.7.2" + }, + "body": { + "version": "5.1.0", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" }, - "engines": { - "node": ">=8" + "dependencies": { + "bytes": { + "version": "1.0.0", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + } + }, + "string_decoder": { + "version": "0.10.31", + "dev": true + } } }, - "node_modules/karma/node_modules/wrap-ansi": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" + "body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "requires": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "dependencies": { + "debug": { + "version": "2.6.9", + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "ms": { + "version": "2.0.0" + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, - "node_modules/karma/node_modules/yargs": { - "version": "16.2.0", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" + "boolbase": { + "version": "1.0.0", + "dev": true + }, + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/karma/node_modules/yargs-parser": { - "version": "20.2.9", - "license": "ISC", - "engines": { - "node": ">=10" + "braces": { + "version": "3.0.3", + "dev": true, + "requires": { + "fill-range": "^7.1.1" } }, - "node_modules/keycode": { - "version": "2.2.1", - "dev": true, - "license": "MIT" + "browser-stdout": { + "version": "1.3.1", + "dev": true }, - "node_modules/keyv": { - "version": "4.5.4", + "browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "requires": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + } + }, + "browserstack": { + "version": "1.5.3", "dev": true, - "license": "MIT", + "requires": { + "https-proxy-agent": "^2.2.1" + }, "dependencies": { - "json-buffer": "3.0.1" + "agent-base": { + "version": "4.3.0", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + } } }, - "node_modules/kind-of": { - "version": "6.0.3", + "browserstack-local": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.11.tgz", + "integrity": "sha512-RNq0yrezPq7BXXxl/cvsbORfswUQi744po6ECkTEC2RkqNbdPyzewdy4VR9k4QHSzPHTkZx8PeH08veRtfFI8A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "tree-kill": "^1.2.2" } }, - "node_modules/klona": { - "version": "2.0.6", - "license": "MIT", - "engines": { - "node": ">= 8" - } + "buffer-crc32": { + "version": "0.2.13", + "dev": true }, - "node_modules/last-run": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } + "buffer-from": { + "version": "1.1.2", + "dev": true }, - "node_modules/lazystream": { + "bufferstreams": { "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" + "requires": { + "readable-stream": "^1.0.33" }, - "engines": { - "node": ">= 0.6.3" + "dependencies": { + "isarray": { + "version": "0.0.1" + }, + "readable-stream": { + "version": "1.1.14", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31" + } } }, - "node_modules/lead": { - "version": "4.0.0", + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.8", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" } }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" + "call-bind-apply-helpers": { + "version": "1.0.2", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" } }, - "node_modules/lie": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" + "call-bound": { + "version": "1.0.4", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, - "node_modules/liftoff": { - "version": "5.0.1", + "callsites": { + "version": "3.1.0", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "dev": true + }, + "can-autoplay": { + "version": "3.0.2", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==" + }, + "chai": { + "version": "4.4.1", "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^5.0.0", - "fined": "^2.0.0", - "flagged-respawn": "^2.0.0", - "is-plain-object": "^5.0.0", - "rechoir": "^0.8.0", - "resolve": "^1.20.0" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" } }, - "node_modules/lines-and-columns": { - "version": "2.0.4", + "chalk": { + "version": "2.4.2", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "node_modules/live-connect-common": { - "version": "4.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">=20" + "chardet": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "dev": true + }, + "check-error": { + "version": "1.0.3", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" } }, - "node_modules/live-connect-js": { - "version": "7.2.0", - "license": "Apache-2.0", - "dependencies": { - "live-connect-common": "^v4.1.0", - "tiny-hashes": "1.0.1" + "cheerio": { + "version": "1.0.0", + "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" }, - "engines": { - "node": ">=20" + "dependencies": { + "parse5": { + "version": "7.1.2", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } } }, - "node_modules/livereload-js": { - "version": "2.4.0", + "cheerio-select": { + "version": "2.1.0", "dev": true, - "license": "MIT" + "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" + } }, - "node_modules/loader-runner": { - "version": "4.3.0", + "chokidar": { + "version": "3.6.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" + "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" } }, - "node_modules/loader-utils": { - "version": "2.0.4", + "chrome-trace-event": { + "version": "1.0.4", + "dev": true + }, + "chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" + "requires": { + "mitt": "^3.0.1", + "zod": "^3.24.1" } }, - "node_modules/locate-app": { - "version": "2.4.15", + "ci-info": { + "version": "3.9.0", + "dev": true + }, + "cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true + }, + "cliui": { + "version": "8.0.1", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://buymeacoffee.com/hejny" + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } }, - { - "type": "github", - "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing" + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } } - ], - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@promptbook/utils": "0.50.0-10", - "type-fest": "2.13.0", - "userhome": "1.0.0" } }, - "node_modules/locate-app/node_modules/type-fest": { - "version": "2.13.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "clone": { + "version": "2.1.2", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "dev": true }, - "node_modules/locate-path": { - "version": "5.0.0", + "clone-deep": { + "version": "4.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, - "engines": { - "node": ">=8" + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } } }, - "node_modules/lodash": { - "version": "4.17.21", - "license": "MIT" + "clone-stats": { + "version": "1.0.0", + "dev": true }, - "node_modules/lodash.clone": { - "version": "4.5.0", + "cloneable-readable": { + "version": "1.1.3", "dev": true, - "license": "MIT" + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", + "color-convert": { + "version": "1.9.3", "dev": true, - "license": "MIT" + "requires": { + "color-name": "1.1.3" + } }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "license": "MIT" + "color-name": { + "version": "1.1.3", + "dev": true }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "dev": true, - "license": "MIT" + "color-support": { + "version": "1.1.3", + "dev": true }, - "node_modules/lodash.get": { - "version": "4.4.2", - "dev": true, - "license": "MIT" + "colors": { + "version": "1.4.0", + "dev": true }, - "node_modules/lodash.isequal": { - "version": "4.5.0", + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "license": "MIT" + "requires": { + "delayed-stream": "~1.0.0" + } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, - "node_modules/lodash.pickby": { - "version": "4.6.0", - "dev": true, - "license": "MIT" + "comment-parser": { + "version": "1.4.1", + "dev": true }, - "node_modules/lodash.some": { - "version": "4.6.0", - "dev": true, - "license": "MIT" + "commondir": { + "version": "1.0.1", + "dev": true }, - "node_modules/lodash.union": { - "version": "4.6.0", + "compress-commons": { + "version": "6.0.2", "dev": true, - "license": "MIT" + "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": { + "buffer": { + "version": "6.0.3", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "is-stream": { + "version": "2.0.1", + "dev": true + }, + "readable-stream": { + "version": "4.5.2", + "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", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } }, - "node_modules/lodash.zip": { - "version": "4.2.0", + "concat-map": { + "version": "0.0.1", + "dev": true + }, + "concat-with-sourcemaps": { + "version": "1.1.0", "dev": true, - "license": "MIT" + "requires": { + "source-map": "^0.6.1" + } }, - "node_modules/log-symbols": { - "version": "2.2.0", + "connect": { + "version": "3.7.0", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.0.1" + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, - "engines": { - "node": ">=4" + "dependencies": { + "debug": { + "version": "2.6.9", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.1.2", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "dev": true + } } }, - "node_modules/log4js": { - "version": "6.9.1", - "license": "Apache-2.0", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" + "connect-livereload": { + "version": "0.6.1", + "dev": true + }, + "consolidate": { + "version": "0.15.1", + "requires": { + "bluebird": "^3.1.1" } }, - "node_modules/logform": { - "version": "2.6.0", + "content-disposition": { + "version": "0.5.4", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5" + }, + "continuable-cache": { + "version": "0.3.1", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0" + }, + "cookie": { + "version": "0.7.1" + }, + "cookie-signature": { + "version": "1.0.6" + }, + "copy-props": { + "version": "4.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" + "requires": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" } }, - "node_modules/logform/node_modules/@colors/colors": { - "version": "1.6.0", + "core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==" + }, + "core-js-compat": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", + "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", + "requires": { + "browserslist": "^4.28.0" + } + }, + "core-util-is": { + "version": "1.0.3" + }, + "cors": { + "version": "2.8.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" + "requires": { + "object-assign": "^4", + "vary": "^1" } }, - "node_modules/loglevel": { - "version": "1.9.1", + "cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" + "requires": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" + "dependencies": { + "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 + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "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 + }, + "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 + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + } } }, - "node_modules/loglevel-plugin-prefix": { - "version": "0.8.4", - "dev": true, - "license": "MIT" + "crc-32": { + "version": "1.2.2", + "dev": true }, - "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==", + "crc32-stream": { + "version": "6.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" }, - "bin": { - "loose-envify": "cli.js" + "dependencies": { + "buffer": { + "version": "6.0.3", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.5.2", + "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", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } } }, - "node_modules/loupe": { - "version": "2.3.7", + "create-wdio": { + "version": "9.18.2", + "resolved": "https://registry.npmjs.org/create-wdio/-/create-wdio-9.18.2.tgz", + "integrity": "sha512-atf81YJfyTNAJXsNu3qhpqF4OO43tHGTpr88duAc1Hk4a0uXJAPUYLnYxshOuMnfmeAxlWD+NqGU7orRiXEuJg==", "dev": true, - "license": "MIT", + "requires": { + "chalk": "^5.3.0", + "commander": "^14.0.0", + "cross-spawn": "^7.0.3", + "ejs": "^3.1.10", + "execa": "^9.6.0", + "import-meta-resolve": "^4.1.0", + "inquirer": "^12.7.0", + "normalize-package-data": "^7.0.0", + "read-pkg-up": "^10.1.0", + "recursive-readdir": "^2.2.3", + "semver": "^7.6.3", + "type-fest": "^4.41.0", + "yargs": "^17.7.2" + }, "dependencies": { - "get-func-name": "^2.0.1" + "chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "dev": true + }, + "commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "dev": true + }, + "execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + } + }, + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + }, + "hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + } + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "normalize-package-data": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.1.tgz", + "integrity": "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==", + "dev": true, + "requires": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "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, + "requires": { + "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.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "requires": { + "parse-ms": "^4.0.0" + } + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "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 + }, + "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" + } + } } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "license": "ISC", + "cross-spawn": { + "version": "7.0.6", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "dependencies": { - "yallist": "^3.0.2" + "isexe": { + "version": "2.0.0", + "dev": true + }, + "which": { + "version": "2.0.2", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/lru-queue": { - "version": "0.1.0", + "crypto-js": { + "version": "4.2.0" + }, + "css": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "es5-ext": "~0.10.2" + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" } }, - "node_modules/m3u8-parser": { - "version": "4.8.0", + "css-select": { + "version": "5.1.0", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.5", - "global": "^4.4.0" + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" } }, - "node_modules/magic-string": { - "version": "0.30.17", + "css-shorthand-properties": { + "version": "1.1.1", + "dev": true + }, + "css-value": { + "version": "0.0.1", + "dev": true + }, + "css-what": { + "version": "6.1.0", + "dev": true + }, + "csv-writer": { + "version": "1.6.0", + "dev": true + }, + "custom-event": { + "version": "1.0.1", + "dev": true + }, + "d": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "requires": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" } }, - "node_modules/make-dir": { - "version": "3.1.0", + "data-uri-to-buffer": { + "version": "4.0.1", + "dev": true + }, + "data-view-buffer": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" } }, - "node_modules/map-cache": { - "version": "0.2.2", + "data-view-byte-length": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" } }, - "node_modules/map-stream": { - "version": "0.0.7", + "data-view-byte-offset": { + "version": "1.0.1", "dev": true, - "license": "MIT" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" } }, - "node_modules/media-typer": { - "version": "0.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "date-format": { + "version": "4.0.14", + "dev": true + }, + "debounce": { + "version": "1.2.1", + "dev": true + }, + "debug": { + "version": "4.3.6", + "requires": { + "ms": "2.1.2" } }, - "node_modules/memoizee": { - "version": "0.4.17", + "debug-fabulous": { + "version": "1.1.0", "dev": true, - "license": "ISC", - "dependencies": { - "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" + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" }, - "engines": { - "node": ">=0.12" + "dependencies": { + "debug": { + "version": "3.2.7", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "node_modules/memory-fs": { - "version": "0.5.0", + "decamelize": { + "version": "6.0.0", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.2", + "dev": true + }, + "deep-eql": { + "version": "4.1.4", "dev": true, - "license": "MIT", - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" + "requires": { + "type-detect": "^4.0.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "deep-equal": { + "version": "2.2.3", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "deep-is": { + "version": "0.1.4", "dev": true }, - "node_modules/merge2": { - "version": "1.4.1", + "deepmerge-ts": { + "version": "7.1.5", + "dev": true + }, + "defaults": { + "version": "1.0.4", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" + "optional": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "dev": true, + "optional": true + } } }, - "node_modules/methods": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "define-data-property": { + "version": "1.1.4", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, - "node_modules/micromatch": { - "version": "4.0.8", + "define-properties": { + "version": "1.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, - "node_modules/mime": { - "version": "2.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" + "degenerator": { + "version": "5.0.1", + "dev": true, + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, - "engines": { - "node": ">=4.0.0" + "dependencies": { + "escodegen": { + "version": "2.1.0", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "dev": true + } } }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "depd": { + "version": "2.0.0" + }, + "dequal": { + "version": "2.0.3", + "dev": true + }, + "destroy": { + "version": "1.2.0" + }, + "detect-file": { + "version": "1.0.0", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "dev": true + }, + "devtools-protocol": { + "version": "0.0.1464554", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", + "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", + "dev": true + }, + "di": { + "version": "0.0.1", + "dev": true + }, + "diff": { + "version": "5.2.0", + "dev": true + }, + "diff-sequences": { + "version": "29.6.3", + "dev": true }, - "node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } + "dlv": { + "version": "1.1.3" }, - "node_modules/min-document": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", - "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "license": "MIT", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "requires": { + "esutils": "^2.0.2" } }, - "node_modules/minimist": { - "version": "1.2.8", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dom-serialize": { + "version": "2.2.1", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "node_modules/minipass": { - "version": "7.1.2", + "dom-serializer": { + "version": "2.0.0", "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dom-walk": { + "version": "0.1.2", "dev": true }, - "node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } + "domelementtype": { + "version": "2.3.0", + "dev": true }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "domhandler": { + "version": "5.0.3", "dev": true, - "license": "MIT", - "dependencies": { - "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", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" + "requires": { + "domelementtype": "^2.3.0" } }, - "node_modules/mocha/node_modules/ansi-styles": { - "version": "4.3.0", + "domutils": { + "version": "3.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" } }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "dev": true }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "dset": { + "version": "3.1.4" + }, + "dunder-proto": { + "version": "1.0.1", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" } }, - "node_modules/mocha/node_modules/chalk": { - "version": "4.1.2", + "duplexer": { + "version": "0.1.2", + "dev": true + }, + "duplexify": { + "version": "4.1.3", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, - "node_modules/mocha/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", + "each-props": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" } }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", + "eastasianwidth": { + "version": "0.2.0", + "dev": true + }, + "easy-table": { + "version": "1.2.0", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "requires": { + "ansi-regex": "^5.0.1", + "wcwidth": "^1.0.1" } }, - "node_modules/mocha/node_modules/color-convert": { - "version": "2.0.1", + "edge-paths": { + "version": "3.0.5", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "requires": { + "@types/which": "^2.0.1", + "which": "^2.0.2" }, - "engines": { - "node": ">=7.0.0" + "dependencies": { + "isexe": { + "version": "2.0.0", + "dev": true + }, + "which": { + "version": "2.0.2", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/mocha/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", + "edgedriver": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-6.1.2.tgz", + "integrity": "sha512-UvFqd/IR81iPyWMcxXbUNi+xKWR7JjfoHjfuwjqsj9UHQKn80RpQmS0jf+U25IPi+gKVPcpOSKm0XkqgGMq4zQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "requires": { + "@wdio/logger": "^9.1.3", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "fast-xml-parser": "^5.0.8", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "which": "^5.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@wdio/logger": { + "version": "9.15.0", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "agent-base": { + "version": "7.1.3", + "dev": true + }, + "chalk": { + "version": "5.4.1", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + } } }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", + "ee-first": { + "version": "1.1.1" + }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "jake": "^10.8.5" } }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", + "electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==" + }, + "emoji-regex": { + "version": "8.0.0", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "dev": true + }, + "encodeurl": { + "version": "1.0.2" + }, + "encoding-sniffer": { + "version": "0.2.0", "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", + "end-of-stream": { + "version": "1.4.4", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "once": "^1.4.0" } }, - "node_modules/mocha/node_modules/is-unicode-supported": { - "version": "0.1.0", + "engine.io": { + "version": "6.6.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "cookie": { + "version": "0.7.2", + "dev": true + } } }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "engine.io-parser": { + "version": "5.2.3", + "dev": true + }, + "enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", + "ent": { + "version": "2.2.0", + "dev": true + }, + "entities": { + "version": "4.5.0", + "dev": true + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "errno": { + "version": "0.1.8", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "prr": "~1.0.1" } }, - "node_modules/mocha/node_modules/log-symbols": { - "version": "4.1.0", + "error": { + "version": "7.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "string-template": "~0.2.1" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", + "error-ex": { + "version": "1.3.2", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "requires": { + "is-arrayish": "^0.2.1" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", + "es-abstract": { + "version": "1.23.9", "dev": true, - "license": "MIT" + "requires": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + } }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", + "es-define-property": { + "version": "1.0.1" + }, + "es-errors": { + "version": "1.3.0" + }, + "es-get-iterator": { + "version": "1.1.3", "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" } }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", + "es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" } }, - "node_modules/mocha/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "es-module-lexer": { + "version": "1.5.3", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "requires": { + "es-errors": "^1.3.0" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", + "es-set-tostringtag": { + "version": "2.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.2", + "dev": true, + "requires": { + "hasown": "^2.0.0" } }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", + "es-to-primitive": { + "version": "1.3.0", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "requires": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", + "es5-ext": { + "version": "0.10.64", "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" } }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.9", + "es6-iterator": { + "version": "2.0.3", "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, - "node_modules/mocha/node_modules/yocto-queue": { - "version": "0.1.0", + "es6-promise": { + "version": "4.2.8" + }, + "es6-promisify": { + "version": "5.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "es6-promise": "^4.0.3" } }, - "node_modules/moment": { - "version": "2.30.1", + "es6-symbol": { + "version": "3.1.4", "dev": true, - "license": "MIT", - "engines": { - "node": "*" + "requires": { + "d": "^1.0.2", + "ext": "^1.7.0" } }, - "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "es6-weak-map": { + "version": "2.0.3", "dev": true, - "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.1.0" - }, - "engines": { - "node": ">= 0.8.0" + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" } }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", + "esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "requires": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" + "escalade": { + "version": "3.2.0" }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", + "escape-html": { + "version": "1.0.3" + }, + "escape-string-regexp": { + "version": "1.0.5", + "dev": true + }, + "escodegen": { + "version": "1.8.1", "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" }, - "engines": { - "node": ">= 0.8" + "dependencies": { + "estraverse": { + "version": "1.9.3", + "dev": true + }, + "levn": { + "version": "0.3.0", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "type-check": { + "version": "0.3.2", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } } }, - "node_modules/mpd-parser": { - "version": "0.22.1", + "eslint": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.5", - "@xmldom/xmldom": "^0.8.3", - "global": "^4.4.0" + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" }, - "bin": { - "mpd-to-m3u8-json": "bin/parse.js" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "dev": true + }, + "eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "dev": true + } } }, - "node_modules/mrmime": { - "version": "2.0.0", + "eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "requires": { + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + } } }, - "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/multimatch": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", - "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", + "eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", "dev": true, - "dependencies": { - "array-differ": "^4.0.0", - "array-union": "^3.0.1", - "minimatch": "^9.0.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" } }, - "node_modules/multimatch/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "eslint-import-resolver-node": { + "version": "0.3.9", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", "dev": true, + "requires": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, "dependencies": { - "balanced-match": "^1.0.0" + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } } }, - "node_modules/multimatch/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "eslint-module-utils": { + "version": "2.12.0", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "requires": { + "debug": "^3.2.7" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "dependencies": { + "debug": { + "version": "3.2.7", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "node_modules/mute-stdout": { - "version": "2.0.0", + "eslint-plugin-chai-friendly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.1.0.tgz", + "integrity": "sha512-+T1rClpDdXkgBAhC16vRQMI5umiWojVqkj9oUTdpma50+uByCZM/oBfxitZiOkjMRlm725mwFfz/RVgyDRvCKA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } + "requires": {} }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "requires": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" } }, - "node_modules/mux.js": { - "version": "6.0.1", + "eslint-plugin-import": { + "version": "2.31.0", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.11.2", - "global": "^4.4.0" - }, - "bin": { - "muxjs-transmux": "bin/transmux.js" + "requires": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" }, - "engines": { - "node": ">=8", - "npm": ">=5" + "dependencies": { + "debug": { + "version": "3.2.7", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "eslint-plugin-import-x": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "requires": { + "@typescript-eslint/types": "^8.35.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.0.1", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + } } }, - "node_modules/natural-compare": { - "version": "1.4.0", + "eslint-plugin-jsdoc": { + "version": "50.6.6", "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "requires": { + "@es-joy/jsdoccomment": "~0.49.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.6", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "dev": true + }, + "semver": { + "version": "7.7.1", + "dev": true + }, + "spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + } } }, - "node_modules/neo-async": { - "version": "2.6.2", + "eslint-plugin-n": { + "version": "17.21.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.3.tgz", + "integrity": "sha512-MtxYjDZhMQgsWRm/4xYLL0i2EhusWT7itDxlJ80l1NND2AL2Vi5Mvneqv/ikG9+zpran0VsVRXTEHrpLmUZRNw==", "dev": true, - "license": "MIT" + "requires": { + "@eslint-community/eslint-utils": "^4.5.0", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "globrex": "^0.1.2", + "ignore": "^5.3.2", + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" + }, + "dependencies": { + "globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + } + } }, - "node_modules/neostandard": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/neostandard/-/neostandard-0.12.2.tgz", - "integrity": "sha512-VZU8EZpSaNadp3rKEwBhVD1Kw8jE3AftQLkCyOaM7bWemL1LwsYRsBnAmXy2LjG9zO8t66qJdqB7ccwwORyrAg==", + "eslint-plugin-promise": { + "version": "7.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "@stylistic/eslint-plugin": "2.11.0", - "eslint-import-resolver-typescript": "^3.10.1", - "eslint-plugin-import-x": "^4.16.1", - "eslint-plugin-n": "^17.20.0", - "eslint-plugin-promise": "^7.2.1", - "eslint-plugin-react": "^7.37.5", - "find-up": "^5.0.0", - "globals": "^15.15.0", - "peowly": "^1.3.2", - "typescript-eslint": "^8.35.1" - }, - "bin": { - "neostandard": "cli.mjs" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^9.0.0" + "requires": { + "@eslint-community/eslint-utils": "^4.4.0" } }, - "node_modules/neostandard/node_modules/find-up": { - "version": "5.0.0", + "eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" + "requires": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } } }, - "node_modules/neostandard/node_modules/globals": { - "version": "15.15.0", + "eslint-scope": { + "version": "5.1.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" } }, - "node_modules/neostandard/node_modules/locate-path": { - "version": "6.0.0", + "eslint-visitor-keys": { + "version": "2.1.0", + "dev": true + }, + "esniff": { + "version": "2.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" } }, - "node_modules/neostandard/node_modules/p-limit": { - "version": "3.1.0", + "espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" + "requires": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + } } }, - "node_modules/neostandard/node_modules/p-locate": { - "version": "5.0.0", + "esprima": { + "version": "2.7.3", + "dev": true + }, + "esquery": { + "version": "1.6.0", "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" + "requires": { + "estraverse": "^5.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "estraverse": { + "version": "5.3.0", + "dev": true + } } }, - "node_modules/neostandard/node_modules/yocto-queue": { - "version": "0.1.0", + "esrecurse": { + "version": "4.3.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "requires": { + "estraverse": "^5.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "estraverse": { + "version": "5.3.0", + "dev": true + } } }, - "node_modules/netmask": { - "version": "2.0.2", + "estraverse": { + "version": "4.3.0", + "dev": true + }, + "esutils": { + "version": "2.0.3" + }, + "etag": { + "version": "1.8.1" + }, + "event-emitter": { + "version": "0.3.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" + "requires": { + "d": "1", + "es5-ext": "~0.10.14" } }, - "node_modules/next-tick": { - "version": "1.1.0", - "dev": true, - "license": "ISC" + "event-target-shim": { + "version": "5.0.1", + "dev": true }, - "node_modules/nice-try": { - "version": "1.0.5", - "dev": true, - "license": "MIT" + "eventemitter3": { + "version": "4.0.7", + "dev": true }, - "node_modules/nise": { - "version": "6.1.1", + "events": { + "version": "3.3.0", + "dev": true + }, + "execa": { + "version": "1.0.0", "dev": true, - "license": "BSD-3-Clause", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" + "cross-spawn": { + "version": "6.0.6", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "dev": true + }, + "semver": { + "version": "5.7.2", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "dev": true + }, + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.2.0", + "expand-tilde": { + "version": "2.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" + "requires": { + "homedir-polyfill": "^1.0.1" } }, - "node_modules/node-domexception": { - "version": "1.0.0", + "expect": { + "version": "29.7.0", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" + "requires": { + "@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" + } + }, + "express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "requires": { + "ms": "2.0.0" + } }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" + "encodeurl": { + "version": "2.0.0" + }, + "ms": { + "version": "2.0.0" } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" } }, - "node_modules/node-fetch": { - "version": "3.3.2", + "ext": { + "version": "1.7.0", "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "requires": { + "type": "^2.7.2" } }, - "node_modules/node-html-parser": { - "version": "6.1.13", + "extend": { + "version": "3.0.2", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", "dev": true, - "license": "MIT", + "requires": { + "kind-of": "^1.1.0" + }, "dependencies": { - "css-select": "^5.1.0", - "he": "1.2.0" + "kind-of": { + "version": "1.1.0", + "dev": true + } } }, - "node_modules/node-releases": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.25.tgz", - "integrity": "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==", - "license": "MIT" - }, - "node_modules/node-request-interceptor": { - "version": "0.6.3", + "extract-zip": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, "dependencies": { - "@open-draft/until": "^1.0.3", - "debug": "^4.3.0", - "headers-utils": "^1.2.0", - "strict-event-emitter": "^0.1.0" + "get-stream": { + "version": "5.2.0", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "yauzl": { + "version": "2.10.0", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } } }, - "node_modules/node.extend": { - "version": "2.0.2", - "license": "(MIT OR GPL-2.0)", - "dependencies": { - "has": "^1.0.3", - "is": "^3.2.1" - }, - "engines": { - "node": ">=0.4.0" + "faker": { + "version": "5.5.3", + "dev": true + }, + "fancy-log": { + "version": "2.0.0", + "dev": true, + "requires": { + "color-support": "^1.1.3" } }, - "node_modules/nopt": { - "version": "3.0.6", + "fast-deep-equal": { + "version": "3.1.3" + }, + "fast-fifo": { + "version": "1.3.2", + "dev": true + }, + "fast-glob": { + "version": "3.3.3", "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" } }, - "node_modules/normalize-package-data": { - "version": "6.0.1", + "fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "dev": true + }, + "fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==" + }, + "fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "dev": true + }, + "fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "requires": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + } + }, + "fastest-levenshtein": { + "version": "1.0.16", + "dev": true + }, + "fastq": { + "version": "1.19.1", + "dev": true, + "requires": { + "reusify": "^1.0.4" } }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.6.2", + "faye-websocket": { + "version": "0.10.0", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "requires": { + "websocket-driver": ">=0.5.1" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "fd-slicer": { + "version": "1.1.0", + "dev": true, + "requires": { + "pend": "~1.2.0" } }, - "node_modules/now-and-later": { - "version": "3.0.0", + "fecha": { + "version": "4.2.3", + "dev": true + }, + "fetch-blob": { + "version": "3.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, - "engines": { - "node": ">= 10.13.0" + "dependencies": { + "web-streams-polyfill": { + "version": "3.3.3", + "dev": true + } } }, - "node_modules/npm-run-path": { - "version": "2.0.2", + "figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" + "requires": { + "is-unicode-supported": "^2.0.0" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", + "file-entry-cache": { + "version": "8.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "requires": { + "flat-cache": "^4.0.0" } }, - "node_modules/nth-check": { - "version": "2.1.1", + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" + "requires": { + "minimatch": "^5.0.1" }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "fill-range": { + "version": "7.1.1", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "finalhandler": { + "version": "1.3.1", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "debug": { + "version": "2.6.9", + "requires": { + "ms": "2.0.0" + } + }, + "encodeurl": { + "version": "2.0.0" + }, + "ms": { + "version": "2.0.0" + } } }, - "node_modules/object-is": { - "version": "1.1.6", + "find-cache-dir": { + "version": "3.3.2", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, - "node_modules/object-keys": { - "version": "1.1.1", + "find-up": { + "version": "4.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "node_modules/object.assign": { - "version": "4.1.7", + "findup-sync": { + "version": "5.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" } }, - "node_modules/object.defaults": { - "version": "1.1.0", + "fined": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" } }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "flagged-respawn": { + "version": "2.0.0", + "dev": true + }, + "flat": { + "version": "5.0.2", + "dev": true + }, + "flat-cache": { + "version": "4.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" } }, - "node_modules/object.fromentries": { - "version": "2.0.8", + "flatted": { + "version": "3.3.1", + "dev": true + }, + "follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true + }, + "for-each": { + "version": "0.3.5", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "is-callable": "^1.2.7" } }, - "node_modules/object.groupby": { - "version": "1.0.3", + "for-in": { + "version": "1.0.2", + "dev": true + }, + "for-own": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "for-in": "^1.0.1" } }, - "node_modules/object.pick": { - "version": "1.3.0", + "foreground-child": { + "version": "3.3.0", "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" }, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "dev": true + } } }, - "node_modules/object.values": { - "version": "1.2.1", + "fork-stream": { + "version": "0.0.4", + "dev": true + }, + "form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" + "formdata-node": { + "version": "5.0.1", + "dev": true, + "requires": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "formdata-polyfill": { + "version": "4.0.10", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" + "requires": { + "fetch-blob": "^3.1.2" } }, - "node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } + "forwarded": { + "version": "0.2.0" }, - "node_modules/opener": { - "version": "1.5.2", + "fresh": { + "version": "0.5.2" + }, + "fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", "dev": true, - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "bin/opener-bin.js" + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, - "node_modules/opn": { - "version": "5.5.0", + "fs-mkdirp-stream": { + "version": "2.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" + "requires": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" } }, - "node_modules/opn/node_modules/is-wsl": { + "fs-readfile-promise": { + "version": "3.0.1", + "requires": { + "graceful-fs": "^4.1.11" + } + }, + "fs.realpath": { + "version": "1.0.0", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "optional": true + }, + "fun-hooks": { "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "requires": { + "typescript-tuple": "^2.2.1" } }, - "node_modules/optionator": { - "version": "0.9.4", + "function-bind": { + "version": "1.1.2" + }, + "function.prototype.name": { + "version": "1.1.8", "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" } }, - "node_modules/own-keys": { - "version": "1.0.1", + "functions-have-names": { + "version": "1.2.3", + "dev": true + }, + "geckodriver": { + "version": "5.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "@wdio/logger": "^9.1.3", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.6", + "which": "^5.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@wdio/logger": { + "version": "9.15.0", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "agent-base": { + "version": "7.1.3", + "dev": true + }, + "chalk": { + "version": "5.4.1", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + } } }, - "node_modules/p-finally": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "gensync": { + "version": "1.0.0-beta.2" + }, + "get-caller-file": { + "version": "2.0.5", + "dev": true + }, + "get-func-name": { + "version": "2.0.2", + "dev": true + }, + "get-intrinsic": { + "version": "1.3.0", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "get-package-type": { + "version": "0.1.0", + "dev": true + }, + "get-port": { + "version": "7.1.0", + "dev": true + }, + "get-proto": { + "version": "1.0.1", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, - "node_modules/p-locate": { + "get-stream": { "version": "4.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "requires": { + "pump": "^3.0.0" } }, - "node_modules/p-try": { - "version": "2.2.0", + "get-symbol-description": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", + "get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" + "requires": { + "resolve-pkg-maps": "^1.0.0" } }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", + "get-uri": { + "version": "6.0.4", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "dependencies": { + "data-uri-to-buffer": { + "version": "6.0.2", + "dev": true + } } }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", + "git-repo-info": { + "version": "2.1.1", + "dev": true + }, + "gitconfiglocal": { + "version": "2.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "requires": { + "ini": "^1.3.2" }, - "engines": { - "node": ">= 14" + "dependencies": { + "ini": { + "version": "1.3.8", + "dev": true + } } }, - "node_modules/pac-resolver": { - "version": "7.0.1", + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" + "requires": { + "foreground-child": "^3.1.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" }, - "engines": { - "node": ">= 14" + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + } } }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "1.0.11", + "glob-parent": { + "version": "5.1.2", "dev": true, - "license": "(MIT AND Zlib)" + "requires": { + "is-glob": "^4.0.1" + } }, - "node_modules/parent-module": { - "version": "1.0.1", + "glob-stream": { + "version": "8.0.3", "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" + "requires": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" }, - "engines": { - "node": ">=6" + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } } }, - "node_modules/parse-filepath": { - "version": "1.0.2", + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "glob-watcher": { + "version": "6.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" + "requires": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" } }, - "node_modules/parse-imports": { - "version": "2.2.1", + "global": { + "version": "4.4.0", "dev": true, - "license": "Apache-2.0 AND MIT", - "dependencies": { - "es-module-lexer": "^1.5.3", - "slashes": "^3.0.12" - }, - "engines": { - "node": ">= 18" + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" } }, - "node_modules/parse-json": { - "version": "7.1.1", + "global-modules": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.21.4", - "error-ex": "^1.3.2", - "json-parse-even-better-errors": "^3.0.0", - "lines-and-columns": "^2.0.3", - "type-fest": "^3.8.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" } }, - "node_modules/parse-json/node_modules/type-fest": { - "version": "3.13.1", + "global-prefix": { + "version": "1.0.2", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=14.16" + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "ini": { + "version": "1.3.8", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/parse-ms": { - "version": "2.1.0", + "globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true + }, + "globalthis": { + "version": "1.0.4", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" } }, - "node_modules/parse-node-version": { - "version": "1.0.1", + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "glogg": { + "version": "2.2.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" + "requires": { + "sparkles": "^2.1.0" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", + "gopd": { + "version": "1.2.0" + }, + "graceful-fs": { + "version": "4.2.11" + }, + "grapheme-splitter": { + "version": "1.0.4", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "gulp": { + "version": "5.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.1.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.2" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" + "gulp-babel": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", + "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", + "requires": { + "plugin-error": "^1.0.1", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "7.1.2", + "gulp-clean": { + "version": "0.4.0", "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^4.4.0" + "requires": { + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2", + "rimraf": "^2.6.2", + "through2": "^2.0.3", + "vinyl": "^2.1.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "dependencies": { + "fancy-log": { + "version": "1.3.3", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "glob": { + "version": "7.2.3", + "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" + } + }, + "plugin-error": { + "version": "0.1.2", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, + "rimraf": { + "version": "2.7.1", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "through2": { + "version": "2.0.5", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", + "gulp-cli": { + "version": "3.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" + "requires": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.1", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "7.0.4", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "dev": true + } } }, - "node_modules/parse5-parser-stream/node_modules/parse5": { - "version": "7.1.2", + "gulp-concat": { + "version": "2.6.1", "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^4.4.0" + "requires": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "dependencies": { + "through2": { + "version": "2.0.5", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "node_modules/parseurl": { - "version": "1.3.3", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "gulp-connect": { + "version": "5.7.0", + "dev": true, + "requires": { + "ansi-colors": "^2.0.5", + "connect": "^3.6.6", + "connect-livereload": "^0.6.0", + "fancy-log": "^1.3.2", + "map-stream": "^0.0.7", + "send": "0.19.0", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2", + "tiny-lr": "^1.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "2.0.5", + "dev": true + }, + "fancy-log": { + "version": "1.3.3", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + } } }, - "node_modules/path-exists": { - "version": "4.0.0", + "gulp-filter": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-9.0.1.tgz", + "integrity": "sha512-knVYL8h9bfYIeft3VokVTkuaWJkQJMrFCS3yVjZQC6BGg+1dZFoeUY++B9D2X4eFpeNTx9StWK0qnDby3NO3PA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "multimatch": "^7.0.0", + "plugin-error": "^2.0.1", + "slash": "^5.1.0", + "streamfilter": "^3.0.0", + "to-absolute-glob": "^3.0.0" + } + }, + "gulp-if": { + "version": "3.0.0", + "dev": true, + "requires": { + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" + }, + "dependencies": { + "through2": { + "version": "3.0.2", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, - "node_modules/path-is-absolute": { + "gulp-js-escape": { "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dev": true, + "requires": { + "through2": "^0.6.3" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "dev": true + }, + "through2": { + "version": "0.6.5", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } } }, - "node_modules/path-key": { - "version": "3.1.1", + "gulp-match": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "minimatch": "^3.0.3" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "license": "MIT" + "gulp-rename": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.1.0.tgz", + "integrity": "sha512-dGuzuH8jQGqCMqC544IEPhs5+O2l+IkdoSZsgd4kY97M1CxQeI3qrmweQBIrxLBbjbe/8uEWK8HHcNBc3OCy4g==", + "dev": true }, - "node_modules/path-root": { - "version": "0.1.1", + "gulp-replace": { + "version": "1.1.4", "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "requires": { + "@types/node": "*", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" } }, - "node_modules/path-root-regex": { - "version": "0.1.2", + "gulp-sourcemaps": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "dev": true + }, + "through2": { + "version": "2.0.5", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "node_modules/path-scurry": { - "version": "1.11.1", + "gulp-tap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-tap/-/gulp-tap-2.0.0.tgz", + "integrity": "sha512-U5/v1bTozx672QHzrvzPe6fPl2io7Wqyrx2y30AG53eMU/idH4BrY/b2yikOkdyhjDqGgPoMUMnpBg9e9LK8Nw==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" + "requires": { + "through2": "^3.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "dependencies": { + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "license": "ISC" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "license": "MIT" + "gulp-wrap": { + "version": "0.15.0", + "requires": { + "consolidate": "^0.15.1", + "es6-promise": "^4.2.6", + "fs-readfile-promise": "^3.0.1", + "js-yaml": "^3.13.0", + "lodash": "^4.17.11", + "node.extend": "2.0.2", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tryit": "^1.0.1", + "vinyl-bufferstream": "^1.0.1" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "4.0.0" + }, + "arr-union": { + "version": "3.1.0" + }, + "extend-shallow": { + "version": "3.0.2", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "plugin-error": { + "version": "1.0.1", + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "through2": { + "version": "3.0.2", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } }, - "node_modules/pathe": { - "version": "1.1.2", + "gulplog": { + "version": "2.2.0", "dev": true, - "license": "MIT" + "requires": { + "glogg": "^2.2.0" + } }, - "node_modules/pathval": { - "version": "1.1.1", + "gzip-size": { + "version": "6.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": "*" + "requires": { + "duplexer": "^0.1.2" } }, - "node_modules/pause-stream": { - "version": "0.0.11", + "handlebars": { + "version": "4.7.8", "dev": true, - "license": [ - "MIT", - "Apache2" - ], - "dependencies": { - "through": "~2.3" + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" } }, - "node_modules/pend": { - "version": "1.2.0", - "dev": true, - "license": "MIT" + "has": { + "version": "1.0.4" }, - "node_modules/peowly": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.6.0" - } + "has-bigints": { + "version": "1.0.2", + "dev": true }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" + "has-flag": { + "version": "3.0.0", + "dev": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" } }, - "node_modules/pirates": { - "version": "4.0.6", + "has-proto": { + "version": "1.2.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" + "requires": { + "dunder-proto": "^1.0.0" } }, - "node_modules/pkcs7": { - "version": "1.0.4", + "has-symbols": { + "version": "1.1.0" + }, + "has-tostringtag": { + "version": "1.0.2", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.5.5" - }, - "bin": { - "pkcs7": "bin/cli.js" + "requires": { + "has-symbols": "^1.0.3" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "hasown": { + "version": "2.0.2", + "requires": { + "function-bind": "^1.1.2" } }, - "node_modules/plugin-error": { - "version": "2.0.1", + "he": { + "version": "1.2.0", + "dev": true + }, + "headers-utils": { + "version": "1.2.5", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "parse-passwd": "^1.0.0" } }, - "node_modules/plugin-error/node_modules/ansi-colors": { - "version": "1.1.0", + "hosted-git-info": { + "version": "7.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-wrap": "^0.1.0" + "requires": { + "lru-cache": "^10.0.1" }, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "lru-cache": { + "version": "10.2.2", + "dev": true + } } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", + "html-escaper": { + "version": "2.0.2", + "dev": true + }, + "htmlfy": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.8.1.tgz", + "integrity": "sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==", + "dev": true + }, + "htmlparser2": { + "version": "9.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" } }, - "node_modules/postcss": { - "version": "7.0.39", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "http-errors": { + "version": "2.0.0", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, - "node_modules/postcss/node_modules/picocolors": { - "version": "0.2.1", - "dev": true, - "license": "ISC" + "http-parser-js": { + "version": "0.5.10", + "dev": true }, - "node_modules/prelude-ls": { - "version": "1.2.1", + "http-proxy": { + "version": "1.18.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" } }, - "node_modules/prettier": { - "version": "2.8.1", + "http-proxy-agent": { + "version": "7.0.2", "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "dependencies": { + "agent-base": { + "version": "7.1.1", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + } } }, - "node_modules/pretty-format": { - "version": "29.7.0", + "https-proxy-agent": { + "version": "5.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "agent-base": "6", + "debug": "4" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true + }, + "iab-adcom": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/iab-adcom/-/iab-adcom-1.0.6.tgz", + "integrity": "sha512-XAJdidfrFgZNKmHqcXD3Zhqik2rdSmOs+PGgeVfPWgthxvzNBQxkZnKkW3QAau6mrLjtJc8yOQC6awcEv7gryA==" + }, + "iab-native": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iab-native/-/iab-native-1.0.0.tgz", + "integrity": "sha512-AxGYpKGRcyG5pbEAqj+ssxNwZAfxC0pRwyKc0MYoKjm0UeOoUNCWrZV0HGimcQii6ebe6MRqBQEeENyHM4qTdQ==" + }, + "iab-openrtb": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iab-openrtb/-/iab-openrtb-1.0.1.tgz", + "integrity": "sha512-egawJx6+pMh/6uA/hak1y+R2+XCSH2jxteSkWlY98/XdQQftaMUMllUFNMKrHwq9lgCI70Me06g4JCCnV6E62g==", + "requires": { + "iab-adcom": "1.0.6" } }, - "node_modules/pretty-ms": { - "version": "7.0.1", + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "dev": true + }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", "dev": true, - "license": "MIT", - "dependencies": { - "parse-ms": "^2.1.0" - }, - "engines": { - "node": ">=10" + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "dev": true + } } }, - "node_modules/process": { - "version": "0.11.10", + "import-meta-resolve": { + "version": "4.1.0", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "dev": true + }, + "individual": { + "version": "2.0.0", + "dev": true + }, + "inflight": { + "version": "1.0.6", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" + "requires": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "license": "MIT" + "inherits": { + "version": "2.0.4" }, - "node_modules/progress": { - "version": "2.0.3", + "inquirer": { + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.0.tgz", + "integrity": "sha512-LlFVmvWVCun7uEgPB3vups9NzBrjJn48kRNtFGw3xU1H5UXExTEz/oF1JGLaB0fvlkUB+W6JfgLcSEaSdH7RPA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/prompts": "^7.8.0", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "mute-stream": "^2.0.0", + "run-async": "^4.0.5", + "rxjs": "^7.8.2" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "internal-slot": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" + "interpret": { + "version": "3.1.1", + "dev": true }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "ip-address": { + "version": "9.0.5", + "dev": true, + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" }, - "engines": { - "node": ">= 0.10" + "dependencies": { + "sprintf-js": { + "version": "1.1.3", + "dev": true + } } }, - "node_modules/proxy-agent": { - "version": "6.5.0", + "ipaddr.js": { + "version": "1.9.1" + }, + "is": { + "version": "3.3.0" + }, + "is-absolute": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" } }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.3", + "is-arguments": { + "version": "1.1.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", + "is-array-buffer": { + "version": "3.0.5", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" } }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", + "is-arrayish": { + "version": "0.2.1", + "dev": true + }, + "is-async-function": { + "version": "2.1.1", "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" + "requires": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" } }, - "node_modules/proxy-from-env": { + "is-bigint": { "version": "1.1.0", "dev": true, - "license": "MIT" + "requires": { + "has-bigints": "^1.0.2" + } }, - "node_modules/prr": { - "version": "1.0.1", + "is-binary-path": { + "version": "2.1.0", "dev": true, - "license": "MIT" + "requires": { + "binary-extensions": "^2.0.0" + } }, - "node_modules/ps-tree": { - "version": "1.2.0", + "is-boolean-object": { + "version": "1.2.2", "dev": true, - "license": "MIT", - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" } }, - "node_modules/pump": { - "version": "3.0.0", + "is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", "dev": true, - "license": "MIT", + "requires": { + "semver": "^7.7.1" + }, "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + } } }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "is-callable": { + "version": "1.2.7", + "dev": true + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "requires": { + "hasown": "^2.0.2" } }, - "node_modules/puppeteer": { - "version": "24.11.2", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.11.2.tgz", - "integrity": "sha512-HopdRZWHa5zk0HSwd8hU+GlahQ3fmesTAqMIDHVY9HasCvppcYuHYXyjml0nlm+nbwVCqAQWV+dSmiNCrZGTGQ==", + "is-data-view": { + "version": "1.0.2", "dev": true, - "hasInstallScript": true, - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1464554", - "puppeteer-core": "24.11.2", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" + "requires": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" } }, - "node_modules/puppeteer-core": { - "version": "24.11.2", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.11.2.tgz", - "integrity": "sha512-c49WifNb8hix+gQH17TldmD6TC/Md2HBaTJLHexIUq4sZvo2pyHY/Pp25qFQjibksBu/SJRYUY7JsoaepNbiRA==", + "is-date-object": { + "version": "1.1.0", "dev": true, - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "debug": "^4.4.1", - "devtools-protocol": "0.0.1464554", - "typed-query-selector": "^2.12.0", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" } }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" + "is-docker": { + "version": "2.2.1", + "dev": true + }, + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "requires": { + "isobject": "^3.0.1" + } } } }, - "node_modules/puppeteer-core/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==", + "is-extglob": { + "version": "2.1.1", "dev": true }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "is-finalizationregistry": { + "version": "1.1.1", "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 - } + "requires": { + "call-bound": "^1.0.3" } }, - "node_modules/q": { - "version": "1.5.1", + "is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "dev": true + }, + "is-generator-function": { + "version": "1.0.10", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" + "requires": { + "has-tostringtag": "^1.0.0" } }, - "node_modules/qjobs": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">=0.9" + "is-glob": { + "version": "4.0.3", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" } }, - "node_modules/qs": { - "version": "6.13.0", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "is-map": { + "version": "2.0.3", + "dev": true }, - "node_modules/query-selector-shadow-dom": { - "version": "1.0.1", + "is-nan": { + "version": "1.3.2", "dev": true, - "license": "MIT" + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } }, - "node_modules/querystringify": { - "version": "2.2.0", - "dev": true, - "license": "MIT" + "is-negated-glob": { + "version": "1.0.0", + "dev": true }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "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" - } - ], - "license": "MIT" + "is-number": { + "version": "7.0.0", + "dev": true }, - "node_modules/randombytes": { - "version": "2.1.0", + "is-number-object": { + "version": "1.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" } }, - "node_modules/range-parser": { + "is-plain-obj": { + "version": "4.1.0", + "dev": true + }, + "is-plain-object": { + "version": "5.0.0", + "dev": true + }, + "is-promise": { + "version": "2.2.2", + "dev": true + }, + "is-regex": { "version": "1.2.1", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" + "is-relative": { + "version": "1.0.0", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" } }, - "node_modules/react-is": { - "version": "18.3.1", - "dev": true, - "license": "MIT" + "is-running": { + "version": "2.1.0", + "dev": true }, - "node_modules/read-pkg": { - "version": "8.1.0", + "is-set": { + "version": "2.0.3", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.4", "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^6.0.0", - "parse-json": "^7.0.0", - "type-fest": "^4.2.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "call-bound": "^1.0.3" } }, - "node_modules/read-pkg-up": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", - "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", + "is-stream": { + "version": "1.1.0", + "dev": true + }, + "is-string": { + "version": "1.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^8.1.0", - "type-fest": "^4.2.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "6.3.0", + "is-symbol": { + "version": "1.1.1", "dev": true, - "license": "MIT", - "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" + "requires": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "7.2.0", + "is-typed-array": { + "version": "1.1.15", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "which-typed-array": "^1.1.16" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "4.0.0", + "is-unc-path": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "unc-path-regex": "^0.1.2" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "6.0.0", + "is-unicode-supported": { + "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-valid-glob": { + "version": "1.0.0", + "dev": true + }, + "is-weakmap": { + "version": "2.0.2", + "dev": true + }, + "is-weakref": { + "version": "1.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "call-bound": "^1.0.3" } }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "5.0.0", + "is-weakset": { + "version": "2.0.3", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "requires": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" } }, - "node_modules/readable-stream": { - "version": "2.3.8", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "is-windows": { + "version": "1.0.2", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "dev": true, + "requires": { + "is-docker": "^2.0.0" } }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "license": "MIT" + "isarray": { + "version": "2.0.5", + "dev": true }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" + "isbinaryfile": { + "version": "4.0.10", + "dev": true }, - "node_modules/readdir-glob": { - "version": "1.1.3", + "isexe": { + "version": "3.1.1", + "dev": true + }, + "isobject": { + "version": "3.0.1" + }, + "istanbul": { + "version": "0.4.5", "dev": true, - "license": "Apache-2.0", + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, "dependencies": { - "minimatch": "^5.1.0" + "glob": { + "version": "5.0.15", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" } }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", + "istanbul-lib-report": { + "version": "3.0.1", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, - "engines": { - "node": ">=10" + "dependencies": { + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "make-dir": { + "version": "4.0.0", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "semver": { + "version": "7.6.2", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/readdirp": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" } }, - "node_modules/rechoir": { - "version": "0.8.0", + "istanbul-reports": { + "version": "3.1.7", "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "istextorbinary": { + "version": "3.3.0", "dev": true, - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" + "requires": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", + "iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", + "requires": { + "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" + "jackspeak": { + "version": "3.4.3", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", + "jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "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/regexpu-core": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "jest-diff": { + "version": "29.7.0", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "engines": { - "node": ">=4" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/regjsgen": { - "version": "0.8.0", - "license": "MIT" + "jest-get-type": { + "version": "29.6.3", + "dev": true }, - "node_modules/regjsparser": { - "version": "0.12.0", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.0.2" + "jest-matcher-utils": { + "version": "29.7.0", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "bin": { - "regjsparser": "bin/parser" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "jest-message-util": { + "version": "29.7.0", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "engines": { - "node": ">=6" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "slash": { + "version": "3.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", + "jest-mock": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "dev": true, - "license": "ISC" + "peer": true, + "requires": { + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-util": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "peer": true, + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "dev": true, + "peer": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": 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, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "peer": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": 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, + "peer": 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, + "peer": true + }, + "jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + } + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, - "node_modules/replace-ext": { - "version": "2.0.0", + "jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } + "peer": true }, - "node_modules/replace-homedir": { - "version": "2.0.0", + "jest-util": { + "version": "29.7.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/replacestream": { - "version": "4.0.3", + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "license": "BSD-3-Clause", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, "dependencies": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/require-directory": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "js-tokens": { + "version": "4.0.0" }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" + "js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1" + } } }, - "node_modules/requires-port": { - "version": "1.0.0", - "license": "MIT" + "jsbn": { + "version": "1.1.0", + "dev": true }, - "node_modules/resolve": { - "version": "1.22.8", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "jsdoc-type-pratt-parser": { + "version": "4.1.0", + "dev": true }, - "node_modules/resolve-dir": { + "jsesc": { + "version": "3.1.0" + }, + "json-buffer": { + "version": "3.0.1", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "3.0.2", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "dev": true + }, + "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, - "node_modules/resolve-from": { - "version": "5.0.0", + "json5": { + "version": "2.2.3" + }, + "jsonfile": { + "version": "6.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, - "node_modules/resolve-options": { - "version": "2.0.0", + "jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, - "license": "MIT", - "dependencies": { - "value-or-function": "^4.0.0" - }, - "engines": { - "node": ">= 10.13.0" + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", + "jszip": { + "version": "3.10.1", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" } }, - "node_modules/resq": { - "version": "1.11.0", + "just-extend": { + "version": "6.2.0", + "dev": true + }, + "karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, - "license": "MIT", + "requires": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, "dependencies": { - "fast-deep-equal": "^2.0.1" + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "glob": { + "version": "7.2.3", + "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" + } + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "dev": true + } } }, - "node_modules/resq/node_modules/fast-deep-equal": { - "version": "2.0.1", + "karma-babel-preprocessor": { + "version": "8.0.2", "dev": true, - "license": "MIT" + "requires": {} }, - "node_modules/ret": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "karma-browserstack-launcher": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", + "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "requires": { + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" } }, - "node_modules/reusify": { - "version": "1.1.0", + "karma-chai": { + "version": "0.1.0", "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "license": "MIT" + "requires": {} }, - "node_modules/rgb2hex": { - "version": "0.2.5", + "karma-chrome-launcher": { + "version": "3.2.0", "dev": true, - "license": "MIT" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "requires": { + "which": "^1.2.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", "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" + "isexe": { + "version": "2.0.0", + "dev": true + }, + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/run-async": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.5.tgz", - "integrity": "sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==", + "karma-coverage": { + "version": "2.2.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" + "requires": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" } }, - "node_modules/run-parallel": { - "version": "1.2.0", + "karma-coverage-istanbul-reporter": { + "version": "3.0.3", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "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" + } }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "istanbul-lib-source-maps": { + "version": "3.0.6", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "dev": true + } + } }, - { - "type": "consulting", - "url": "https://feross.org/support" + "make-dir": { + "version": "2.1.0", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.2", + "dev": true } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rust-result": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "individual": "^2.0.0" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "karma-firefox-launcher": { + "version": "2.1.3", "dev": true, - "license": "Apache-2.0", + "requires": { + "is-wsl": "^2.2.0", + "which": "^3.0.0" + }, "dependencies": { - "tslib": "^2.1.0" + "isexe": { + "version": "2.0.0", + "dev": true + }, + "which": { + "version": "3.0.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/safaridriver": { - "version": "1.0.0", + "karma-mocha": { + "version": "2.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" + "requires": { + "minimist": "^1.2.3" } }, - "node_modules/safe-array-concat": { - "version": "1.1.3", + "karma-mocha-reporter": { + "version": "2.2.5", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" + "requires": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "strip-ansi": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "dependencies": { + "ansi-regex": { + "version": "3.0.1", + "dev": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "strip-ansi": { + "version": "4.0.0", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } } - ], - "license": "MIT" - }, - "node_modules/safe-json-parse": { - "version": "1.0.1", - "dev": true + } }, - "node_modules/safe-push-apply": { + "karma-opera-launcher": { "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "requires": {} }, - "node_modules/safe-regex-test": { - "version": "1.1.0", + "karma-safari-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", + "integrity": "sha512-qmypLWd6F2qrDJfAETvXDfxHvKDk+nyIjpH9xIeI3/hENr0U3nuqkxaftq73PfXZ4aOuOChA6SnLW4m4AxfRjQ==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "requires": {} }, - "node_modules/safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "karma-safarinative-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-safarinative-launcher/-/karma-safarinative-launcher-1.1.0.tgz", + "integrity": "sha512-vdMjdQDHkSUbOZc8Zq2K5bBC0yJGFEgfrKRJTqt0Um0SC1Rt8drS2wcN6UA3h4LgsL3f1pMcmRSvKucbJE8Qdg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ret": "~0.5.0" - } + "requires": {} }, - "node_modules/safe-stable-stringify": { - "version": "2.4.3", + "karma-script-launcher": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } + "requires": {} }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "license": "MIT" + "karma-sinon": { + "version": "1.0.5", + "dev": true, + "requires": {} }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "karma-sourcemap-loader": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz", + "integrity": "sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.10" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "karma-spec-reporter": { + "version": "0.0.32", + "dev": true, + "requires": { + "colors": "^1.1.2" } }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" + "karma-webpack": { + "version": "5.0.1", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^9.0.3", + "webpack-merge": "^4.1.5" }, - "peerDependencies": { - "ajv": "^8.8.2" + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "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" + }, + "dependencies": { + "minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + }, + "dependencies": { + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + } + } + } } }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "keycode": { + "version": "2.2.1", + "dev": true }, - "node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "keyv": { + "version": "4.5.4", + "dev": true, + "requires": { + "json-buffer": "3.0.1" } }, - "node_modules/semver-greatest-satisfied-range": { + "kind-of": { + "version": "6.0.3", + "dev": true + }, + "klona": { + "version": "2.0.6" + }, + "last-run": { "version": "2.0.0", + "dev": true + }, + "lazystream": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "sver": "^1.8.3" - }, - "engines": { - "node": ">= 10.13.0" + "requires": { + "readable-stream": "^2.0.5" } }, - "node_modules/send": { - "version": "0.16.2", + "lead": { + "version": "4.0.0", + "dev": true + }, + "levn": { + "version": "0.4.1", "dev": true, - "license": "MIT", - "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" + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", + "lie": { + "version": "3.3.0", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "requires": { + "immediate": "~3.0.5" } }, - "node_modules/send/node_modules/depd": { - "version": "1.1.2", + "liftoff": { + "version": "5.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "requires": { + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" } }, - "node_modules/send/node_modules/destroy": { - "version": "1.0.4", + "lines-and-columns": { + "version": "2.0.4", + "dev": true + }, + "live-connect-common": { + "version": "4.1.0" + }, + "live-connect-js": { + "version": "7.2.0", + "requires": { + "live-connect-common": "^v4.1.0", + "tiny-hashes": "1.0.1" + } + }, + "livereload-js": { + "version": "2.4.0", + "dev": true + }, + "loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", "dev": true, - "license": "MIT" + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } }, - "node_modules/send/node_modules/http-errors": { - "version": "1.6.3", + "locate-app": { + "version": "2.4.15", "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "requires": { + "@promptbook/utils": "0.50.0-10", + "type-fest": "2.13.0", + "userhome": "1.0.0" }, - "engines": { - "node": ">= 0.6" + "dependencies": { + "type-fest": { + "version": "2.13.0", + "dev": true + } } }, - "node_modules/send/node_modules/inherits": { - "version": "2.0.3", + "locate-path": { + "version": "5.0.0", "dev": true, - "license": "ISC" + "requires": { + "p-locate": "^4.1.0" + } }, - "node_modules/send/node_modules/mime": { - "version": "1.4.1", + "lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + }, + "lodash.clone": { + "version": "4.5.0", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8" + }, + "lodash.flattendeep": { + "version": "4.4.0", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "dev": true + }, + "lodash.pickby": { + "version": "4.6.0", + "dev": true + }, + "lodash.some": { + "version": "4.6.0", + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "dev": true + }, + "lodash.zip": { + "version": "4.2.0", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" + "requires": { + "chalk": "^2.0.1" } }, - "node_modules/send/node_modules/ms": { - "version": "2.0.0", + "log4js": { + "version": "6.9.1", "dev": true, - "license": "MIT" + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + } }, - "node_modules/send/node_modules/on-finished": { - "version": "2.3.0", + "logform": { + "version": "2.6.0", "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" + "requires": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, - "engines": { - "node": ">= 0.8" + "dependencies": { + "@colors/colors": { + "version": "1.6.0", + "dev": true + } } }, - "node_modules/send/node_modules/setprototypeof": { - "version": "1.1.0", - "dev": true, - "license": "ISC" + "loglevel": { + "version": "1.9.1", + "dev": true }, - "node_modules/send/node_modules/statuses": { + "loglevel-plugin-prefix": { + "version": "0.8.4", + "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, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" } }, - "node_modules/serialize-error": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-12.0.0.tgz", - "integrity": "sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^4.31.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "loupe": { + "version": "2.3.7", + "dev": true, + "requires": { + "get-func-name": "^2.0.1" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "lru-cache": { + "version": "5.1.1", + "requires": { + "yallist": "^3.0.2" } }, - "node_modules/serve-index": { - "version": "1.9.1", + "lru-queue": { + "version": "0.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" + "requires": { + "es5-ext": "~0.10.2" } }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", + "m3u8-parser": { + "version": "4.8.0", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" } }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", + "magic-string": { + "version": "0.30.17", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", + "make-dir": { + "version": "3.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" + "requires": { + "semver": "^6.0.0" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "dev": true, - "license": "ISC" + "map-cache": { + "version": "0.2.2", + "dev": true }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" + "map-stream": { + "version": "0.0.7", + "dev": true }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "dev": true, - "license": "ISC" + "math-intrinsics": { + "version": "1.1.0" }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", + "media-typer": { + "version": "0.3.0" + }, + "memoizee": { + "version": "0.4.17", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "requires": { + "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" } }, - "node_modules/serve-static": { - "version": "1.16.2", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" + "memory-fs": { + "version": "0.5.0", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } + "merge-descriptors": { + "version": "1.0.3" }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "merge-stream": { "version": "2.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "merge2": { + "version": "1.4.1", + "dev": true }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" + "methods": { + "version": "1.1.2" + }, + "micromatch": { + "version": "4.0.8", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" } }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" + "mime": { + "version": "2.6.0", + "dev": true }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "license": "MIT", - "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" + "mime-db": { + "version": "1.52.0" + }, + "mime-types": { + "version": "2.1.35", + "requires": { + "mime-db": "1.52.0" } }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "min-document": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", + "minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "brace-expansion": "^1.1.7" } }, - "node_modules/set-function-name": { - "version": "2.0.2", + "minimist": { + "version": "1.2.8", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "dev": true + }, + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" + "requires": { + "minimist": "^1.2.6" + } + }, + "mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.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" }, - "engines": { - "node": ">= 0.4" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "dev": true + }, + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cliui": { + "version": "7.0.4", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "8.1.0", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "dev": true + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "8.1.1", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "dev": true + } } }, - "node_modules/set-proto": { - "version": "1.0.0", + "moment": { + "version": "2.30.1", + "dev": true + }, + "morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" }, - "engines": { - "node": ">= 0.4" + "dependencies": { + "debug": { + "version": "2.6.9", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + } } }, - "node_modules/setimmediate": { - "version": "1.0.5", + "mpd-parser": { + "version": "0.22.1", "dev": true, - "license": "MIT" + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "@xmldom/xmldom": "^0.8.3", + "global": "^4.4.0" + } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" + "mrmime": { + "version": "2.0.0", + "dev": true }, - "node_modules/shallow-clone": { - "version": "3.0.1", + "ms": { + "version": "2.1.2" + }, + "multimatch": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", + "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" + "requires": { + "array-differ": "^4.0.0", + "array-union": "^3.0.1", + "minimatch": "^9.0.3" }, - "engines": { - "node": ">=8" + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + } } }, - "node_modules/shebang-command": { + "mute-stdout": { "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "dev": true }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true }, - "node_modules/side-channel": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "mux.js": { + "version": "6.0.1", + "dev": true, + "requires": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" } }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "natural-compare": { + "version": "1.4.0", + "dev": true }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "negotiator": { + "version": "0.6.3" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" + "neo-async": { + "version": "2.6.2", + "dev": true }, - "node_modules/sinon": { - "version": "20.0.0", + "neostandard": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/neostandard/-/neostandard-0.12.2.tgz", + "integrity": "sha512-VZU8EZpSaNadp3rKEwBhVD1Kw8jE3AftQLkCyOaM7bWemL1LwsYRsBnAmXy2LjG9zO8t66qJdqB7ccwwORyrAg==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.5", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "supports-color": "^7.2.0" + "requires": { + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@stylistic/eslint-plugin": "2.11.0", + "eslint-import-resolver-typescript": "^3.10.1", + "eslint-plugin-import-x": "^4.16.1", + "eslint-plugin-n": "^17.20.0", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-react": "^7.37.5", + "find-up": "^5.0.0", + "globals": "^15.15.0", + "peowly": "^1.3.2", + "typescript-eslint": "^8.35.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "dependencies": { + "find-up": { + "version": "5.0.0", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "globals": { + "version": "15.15.0", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "yocto-queue": { + "version": "0.1.0", + "dev": true + } } }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } + "netmask": { + "version": "2.0.2", + "dev": true }, - "node_modules/sinon/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "next-tick": { + "version": "1.1.0", + "dev": true }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "nice-try": { + "version": "1.0.5", + "dev": true }, - "node_modules/sirv": { - "version": "2.0.4", + "nise": { + "version": "6.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" + "requires": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" }, - "engines": { - "node": ">= 10" + "dependencies": { + "path-to-regexp": { + "version": "8.2.0", + "dev": true + } } }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "node-domexception": { + "version": "1.0.0", + "dev": true + }, + "node-fetch": { + "version": "3.3.2", "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, - "node_modules/slashes": { - "version": "3.0.12", + "node-html-parser": { + "version": "6.1.13", "dev": true, - "license": "ISC" + "requires": { + "css-select": "^5.1.0", + "he": "1.2.0" + } }, - "node_modules/smart-buffer": { - "version": "4.2.0", + "node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" + }, + "node-request-interceptor": { + "version": "0.6.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "requires": { + "@open-draft/until": "^1.0.3", + "debug": "^4.3.0", + "headers-utils": "^1.2.0", + "strict-event-emitter": "^0.1.0" } }, - "node_modules/socket.io": { - "version": "4.8.0", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" + "node.extend": { + "version": "2.0.2", + "requires": { + "has": "^1.0.3", + "is": "^3.2.1" } }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "license": "MIT", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" + "nopt": { + "version": "3.0.6", + "dev": true, + "requires": { + "abbrev": "1" } }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "normalize-package-data": { + "version": "6.0.1", + "dev": true, + "requires": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, - "engines": { - "node": ">=10.0.0" + "dependencies": { + "semver": { + "version": "7.6.2", + "dev": true + } } }, - "node_modules/socks": { - "version": "2.8.4", + "normalize-path": { + "version": "3.0.0", + "dev": true + }, + "now-and-later": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "requires": { + "once": "^1.4.0" } }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", + "npm-run-path": { + "version": "2.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" + "requires": { + "path-key": "^2.0.0" }, - "engines": { - "node": ">= 14" + "dependencies": { + "path-key": { + "version": "2.0.1", + "dev": true + } } }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", + "nth-check": { + "version": "2.1.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "requires": { + "boolbase": "^1.0.0" } }, - "node_modules/source-list-map": { - "version": "2.0.1", - "dev": true, - "license": "MIT" + "object-assign": { + "version": "4.1.1", + "dev": true }, - "node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } + "object-inspect": { + "version": "1.13.4" }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "object-is": { + "version": "1.1.6", "dev": true, - "engines": { - "node": ">=0.10.0" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" } }, - "node_modules/source-map-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", - "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "object-keys": { + "version": "1.1.1", + "dev": true + }, + "object.assign": { + "version": "4.1.7", "dev": true, - "dependencies": { - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" } }, - "node_modules/source-map-loader/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==", + "object.defaults": { + "version": "1.1.0", "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" } }, - "node_modules/source-map-resolve": { - "version": "0.6.0", + "object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, - "license": "MIT", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" } }, - "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==", + "object.fromentries": { + "version": "2.0.8", "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" } }, - "node_modules/spacetrim": { - "version": "0.11.25", + "object.groupby": { + "version": "1.0.3", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://buymeacoffee.com/hejny" - }, - { - "type": "github", - "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing" - } - ], - "license": "SEE LICENSE IN LICENSE" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + } }, - "node_modules/sparkles": { - "version": "2.1.0", + "object.pick": { + "version": "1.3.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" + "requires": { + "isobject": "^3.0.1" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", + "object.values": { + "version": "1.2.1", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "license": "CC-BY-3.0" + "on-finished": { + "version": "2.4.1", + "requires": { + "ee-first": "1.1.1" + } }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", + "on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true + }, + "once": { + "version": "1.4.0", "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "requires": { + "wrappy": "1" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.18", - "dev": true, - "license": "CC0-1.0" + "opener": { + "version": "1.5.2", + "dev": true }, - "node_modules/split": { - "version": "0.3.3", + "opn": { + "version": "5.5.0", "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" + "requires": { + "is-wsl": "^1.1.0" }, - "engines": { - "node": "*" + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "dev": true + } + } + }, + "optionator": { + "version": "0.9.4", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" } }, - "node_modules/split2": { - "version": "4.2.0", + "own-keys": { + "version": "1.0.1", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" + "requires": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "license": "BSD-3-Clause" + "p-finally": { + "version": "1.0.0", + "dev": true }, - "node_modules/stable-hash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", - "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "p-limit": { + "version": "2.3.0", "dev": true, - "license": "MIT" + "requires": { + "p-try": "^2.0.0" + } }, - "node_modules/stable-hash-x": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", - "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "p-locate": { + "version": "4.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" + "requires": { + "p-limit": "^2.2.0" } }, - "node_modules/stack-utils": { - "version": "2.0.6", + "p-try": { + "version": "2.2.0", + "dev": true + }, + "pac-proxy-agent": { + "version": "7.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" + "requires": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" }, - "engines": { - "node": ">=10" + "dependencies": { + "agent-base": { + "version": "7.1.3", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + } } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", + "pac-resolver": { + "version": "7.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "requires": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" } }, - "node_modules/stop-iteration-iterator": { + "package-json-from-dist": { "version": "1.0.0", + "dev": true + }, + "pako": { + "version": "1.0.11", + "dev": true + }, + "parent-module": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "callsites": "^3.0.0" } }, - "node_modules/stream-buffers": { - "version": "3.0.2", + "parse-filepath": { + "version": "1.0.2", "dev": true, - "license": "Unlicense", - "engines": { - "node": ">= 0.10.0" + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" } }, - "node_modules/stream-combiner": { - "version": "0.0.4", + "parse-imports": { + "version": "2.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "~0.1.1" + "requires": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" } }, - "node_modules/stream-composer": { - "version": "1.0.2", + "parse-json": { + "version": "7.1.1", "dev": true, - "license": "MIT", + "requires": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, "dependencies": { - "streamx": "^2.13.2" + "type-fest": { + "version": "3.13.1", + "dev": true + } } }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "dev": true, - "license": "MIT" + "parse-ms": { + "version": "2.1.0", + "dev": true }, - "node_modules/stream-shift": { - "version": "1.0.3", - "dev": true, - "license": "MIT" + "parse-node-version": { + "version": "1.0.1", + "dev": true }, - "node_modules/streamfilter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", - "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "parse-passwd": { + "version": "1.0.0", + "dev": true + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", "dev": true, - "dependencies": { - "readable-stream": "^3.0.6" + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" }, - "engines": { - "node": ">=8.12.0" + "dependencies": { + "parse5": { + "version": "7.1.2", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } } }, - "node_modules/streamfilter/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==", + "parse5-parser-stream": { + "version": "7.1.2", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "requires": { + "parse5": "^7.0.0" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/streamroller": { - "version": "3.1.5", - "license": "MIT", "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" + "parse5": { + "version": "7.1.2", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } } }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } + "parseurl": { + "version": "1.3.3" }, - "node_modules/streamroller/node_modules/jsonfile": { + "path-exists": { "version": "4.0.0", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "dev": true + }, + "path-parse": { + "version": "1.0.7" + }, + "path-root": { + "version": "0.1.1", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" } }, - "node_modules/streamroller/node_modules/universalify": { + "path-root-regex": { "version": "0.1.2", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } + "dev": true }, - "node_modules/streamx": { - "version": "2.22.0", + "path-scurry": { + "version": "1.11.1", "dev": true, - "license": "MIT", - "dependencies": { - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, - "optionalDependencies": { - "bare-events": "^2.2.0" + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "dev": true + } } }, - "node_modules/strict-event-emitter": { - "version": "0.1.0", - "dev": true, - "license": "MIT" + "path-to-regexp": { + "version": "0.1.12" }, - "node_modules/string_decoder": { + "pathe": { + "version": "1.1.2", + "dev": true + }, + "pathval": { "version": "1.1.1", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } + "dev": true }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" + "pend": { + "version": "1.2.0", + "dev": true }, - "node_modules/string-template": { - "version": "0.2.1", + "peowly": { + "version": "1.3.2", "dev": true }, - "node_modules/string-width": { - "version": "4.2.3", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } + "picocolors": { + "version": "1.1.1" }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } + "picomatch": { + "version": "2.3.1", + "dev": true }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", + "pirates": { + "version": "4.0.6", + "dev": true + }, + "pkcs7": { + "version": "1.0.4", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/runtime": "^7.5.5" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "pkg-dir": { + "version": "4.2.0", + "dev": true, + "requires": { + "find-up": "^4.0.0" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "plugin-error": { + "version": "2.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "ansi-colors": "^1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + } } }, - "node_modules/string.prototype.repeat": { + "possible-typed-array-names": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } + "dev": true }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", + "postcss": { + "version": "7.0.39", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "picocolors": { + "version": "0.2.1", + "dev": true + } } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", + "prelude-ls": { + "version": "1.2.1", + "dev": true + }, + "prettier": { + "version": "2.8.1", + "dev": true + }, + "pretty-format": { + "version": "29.7.0", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "dev": true + } } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", + "pretty-ms": { + "version": "7.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "parse-ms": "^2.1.0" } }, - "node_modules/strip-ansi": { - "version": "7.1.0", + "process": { + "version": "0.11.10", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1" + }, + "progress": { + "version": "2.0.3", + "dev": true + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + } } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "proxy-addr": { + "version": "2.0.7", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.0.1", + "proxy-agent": { + "version": "6.5.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "dependencies": { + "agent-base": { + "version": "7.1.3", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "dev": true + } } }, - "node_modules/strip-bom": { + "proxy-from-env": { + "version": "1.1.0", + "dev": true + }, + "prr": { + "version": "1.0.1", + "dev": true + }, + "pump": { "version": "3.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "punycode": { + "version": "2.3.1", + "dev": true }, - "node_modules/strip-eof": { - "version": "1.0.0", + "puppeteer": { + "version": "24.11.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.11.2.tgz", + "integrity": "sha512-HopdRZWHa5zk0HSwd8hU+GlahQ3fmesTAqMIDHVY9HasCvppcYuHYXyjml0nlm+nbwVCqAQWV+dSmiNCrZGTGQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1464554", + "puppeteer-core": "24.11.2", + "typed-query-selector": "^2.12.0" } }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "puppeteer-core": { + "version": "24.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.11.2.tgz", + "integrity": "sha512-c49WifNb8hix+gQH17TldmD6TC/Md2HBaTJLHexIUq4sZvo2pyHY/Pp25qFQjibksBu/SJRYUY7JsoaepNbiRA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "requires": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1464554", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "requires": {} + } } }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "q": { + "version": "1.5.1", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "dev": true + }, + "qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "requires": { + "side-channel": "^1.1.0" } }, - "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" + "query-selector-shadow-dom": { + "version": "1.0.1", + "dev": true }, - "node_modules/supports-color": { - "version": "5.5.0", + "querystringify": { + "version": "2.2.0", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "dev": true + }, + "randombytes": { + "version": "2.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "requires": { + "safe-buffer": "^5.1.0" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "range-parser": { + "version": "1.2.1" + }, + "raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, - "node_modules/sver": { - "version": "1.8.4", + "react-is": { + "version": "18.3.1", + "dev": true + }, + "read-pkg": { + "version": "8.1.0", "dev": true, - "license": "MIT", - "optionalDependencies": { - "semver": "^6.3.0" + "requires": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" } }, - "node_modules/synckit": { - "version": "0.9.2", + "read-pkg-up": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", + "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" + "requires": { + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" }, - "funding": { - "url": "https://opencollective.com/unts" + "dependencies": { + "find-up": { + "version": "6.3.0", + "dev": true, + "requires": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + } + }, + "locate-path": { + "version": "7.2.0", + "dev": true, + "requires": { + "p-locate": "^6.0.0" + } + }, + "p-limit": { + "version": "4.0.0", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "p-locate": { + "version": "6.0.0", + "dev": true, + "requires": { + "p-limit": "^4.0.0" + } + }, + "path-exists": { + "version": "5.0.0", + "dev": true + } } }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "readable-stream": { + "version": "2.3.8", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "dependencies": { + "isarray": { + "version": "1.0.0" + }, + "safe-buffer": { + "version": "5.1.2" + } } }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "readdir-glob": { + "version": "1.1.3", "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" + "requires": { + "minimatch": "^5.1.0" }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "node_modules/tar-stream": { - "version": "3.1.7", + "readdirp": { + "version": "3.6.0", "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "requires": { + "picomatch": "^2.2.1" } }, - "node_modules/teex": { - "version": "1.0.1", + "rechoir": { + "version": "0.8.0", "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" + "requires": { + "resolve": "^1.20.0" } }, - "node_modules/temp-fs": { - "version": "0.9.9", + "recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dev": true, - "license": "MIT", - "dependencies": { - "rimraf": "~2.5.2" - }, - "engines": { - "node": ">=0.8.0" + "requires": { + "minimatch": "^3.0.5" } }, - "node_modules/temp-fs/node_modules/glob": { - "version": "7.2.3", + "reflect.getprototypeof": { + "version": "1.0.10", "dev": true, - "license": "ISC", - "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" + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" } }, - "node_modules/temp-fs/node_modules/rimraf": { - "version": "2.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "requires": { + "regenerate": "^1.4.2" } }, - "node_modules/ternary-stream": { - "version": "3.0.0", + "regexp.prototype.flags": { + "version": "1.5.4", "dev": true, - "license": "MIT", - "dependencies": { - "duplexify": "^4.1.1", - "fork-stream": "^0.0.4", - "merge-stream": "^2.0.0", - "through2": "^3.0.1" + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" } }, - "node_modules/ternary-stream/node_modules/through2": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" } }, - "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" + "regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" + }, + "regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "requires": { + "jsesc": "~3.1.0" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "remove-trailing-separator": { + "version": "1.1.0", + "dev": true + }, + "replace-ext": { + "version": "2.0.0", + "dev": true + }, + "replace-homedir": { + "version": "2.0.0", + "dev": true + }, + "replacestream": { + "version": "4.0.3", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" } }, - "node_modules/test-exclude": { - "version": "6.0.0", + "require-directory": { + "version": "2.1.1", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "requires-port": { + "version": "1.0.0", + "dev": true + }, + "resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "requires": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", + "resolve-from": { + "version": "5.0.0", + "dev": true + }, + "resolve-options": { + "version": "2.0.0", "dev": true, - "license": "ISC", - "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" + "requires": { + "value-or-function": "^4.0.0" } }, - "node_modules/text-decoder": { - "version": "1.1.0", + "resolve-pkg-maps": { + "version": "1.0.0", + "dev": true + }, + "resq": { + "version": "1.11.0", "dev": true, - "license": "Apache-2.0", + "requires": { + "fast-deep-equal": "^2.0.1" + }, "dependencies": { - "b4a": "^1.6.4" + "fast-deep-equal": { + "version": "2.0.1", + "dev": true + } } }, - "node_modules/textextensions": { - "version": "3.3.0", + "ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true + }, + "reusify": { + "version": "1.1.0", + "dev": true + }, + "rfdc": { + "version": "1.4.1", + "dev": true + }, + "rgb2hex": { + "version": "0.2.5", + "dev": true + }, + "rimraf": { + "version": "3.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "glob": "^7.1.3" }, - "funding": { - "url": "https://bevry.me/fund" + "dependencies": { + "glob": { + "version": "7.2.3", + "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" + } + } } }, - "node_modules/through": { - "version": "2.3.8", - "dev": true, - "license": "MIT" + "run-async": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.5.tgz", + "integrity": "sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==", + "dev": true }, - "node_modules/through2": { - "version": "4.0.2", + "run-parallel": { + "version": "1.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" + "requires": { + "queue-microtask": "^1.2.2" } }, - "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.2", + "rust-result": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "requires": { + "individual": "^2.0.0" } }, - "node_modules/time-stamp": { - "version": "1.1.0", + "rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "tslib": "^2.1.0" } }, - "node_modules/timers-ext": { - "version": "0.1.8", + "safaridriver": { + "version": "1.0.0", + "dev": true + }, + "safe-array-concat": { + "version": "1.1.3", "dev": true, - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.12" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" } }, - "node_modules/tiny-hashes": { + "safe-buffer": { + "version": "5.2.1" + }, + "safe-json-parse": { "version": "1.0.1", - "license": "MIT" + "dev": true }, - "node_modules/tiny-lr": { - "version": "1.1.1", + "safe-push-apply": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" + "requires": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" } }, - "node_modules/tiny-lr/node_modules/debug": { - "version": "3.2.7", + "safe-regex-test": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" } }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "requires": { + "ret": "~0.5.0" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" + "safe-stable-stringify": { + "version": "2.4.3", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2" + }, + "schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true + "dependencies": { + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" } } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "semver": { + "version": "6.3.1" }, - "node_modules/tinyrainbow": { - "version": "1.2.0", + "semver-greatest-satisfied-range": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" + "requires": { + "sver": "^1.8.3" } }, - "node_modules/tmp": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", - "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", - "license": "MIT", - "engines": { - "node": ">=14.14" + "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": { + "debug": { + "version": "2.6.9", + "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==" + } + } + }, + "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==" + } } }, - "node_modules/to-absolute-glob": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-3.0.0.tgz", - "integrity": "sha512-loO/XEWTRqpfcpI7+Jr2RR2Umaaozx1t6OSVWtMi0oy5F/Fxg3IC+D/TToDnxyAGs7uZBGT/6XmyDUxgsObJXA==", + "serialize-error": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-12.0.0.tgz", + "integrity": "sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==", "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "requires": { + "type-fest": "^4.31.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "serialize-javascript": { + "version": "6.0.2", + "dev": true, + "requires": { + "randombytes": "^2.1.0" } }, - "node_modules/to-through": { - "version": "3.0.0", + "serve-index": { + "version": "1.9.1", "dev": true, - "license": "MIT", + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, "dependencies": { - "streamx": "^2.12.5" + "debug": { + "version": "2.6.9", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "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", + "dev": true + }, + "ms": { + "version": "2.0.0", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "dev": true + } + } + }, + "serve-static": { + "version": "1.16.2", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, - "engines": { - "node": ">=10.13.0" + "dependencies": { + "encodeurl": { + "version": "2.0.0" + } } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.6" + "set-function-length": { + "version": "1.2.2", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" } }, - "node_modules/totalist": { - "version": "3.0.1", + "set-function-name": { + "version": "2.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" } }, - "node_modules/triple-beam": { - "version": "1.4.1", + "set-proto": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.0.0" + "requires": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" } }, - "node_modules/tryit": { - "version": "1.0.3", - "license": "MIT" + "setimmediate": { + "version": "1.0.5", + "dev": true }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "setprototypeof": { + "version": "1.2.0" + }, + "shallow-clone": { + "version": "3.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" + "requires": { + "kind-of": "^6.0.2" } }, - "node_modules/ts-declaration-location": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", - "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "shebang-command": { + "version": "2.0.0", "dev": true, - "funding": [ - { - "type": "ko-fi", - "url": "https://ko-fi.com/rebeccastevens" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": ">=4.0.0" + "requires": { + "shebang-regex": "^3.0.0" } }, - "node_modules/ts-declaration-location/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "shebang-regex": { + "version": "3.0.0", + "dev": true + }, + "side-channel": { + "version": "1.1.0", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "side-channel-list": { + "version": "1.0.0", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" } }, - "node_modules/tsconfig-paths/node_modules/json5": { + "side-channel-map": { + "version": "1.0.1", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, - "node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" + "signal-exit": { + "version": "3.0.7", + "dev": true }, - "node_modules/tsx": { - "version": "4.19.3", + "sinon": { + "version": "20.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" + "requires": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.5", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "supports-color": "^7.2.0" }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "dependencies": { + "diff": { + "version": "7.0.0", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/type": { - "version": "2.7.3", + "sirv": { + "version": "2.0.4", "dev": true, - "license": "ISC" + "requires": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + } }, - "node_modules/type-check": { - "version": "0.4.0", + "slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true + }, + "slashes": { + "version": "3.0.12", + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "dev": true + }, + "socket.io": { + "version": "4.8.0", "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" } }, - "node_modules/type-detect": { - "version": "4.0.8", + "socket.io-adapter": { + "version": "2.5.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "requires": { + "debug": "~4.3.4", + "ws": "~8.17.1" } }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "socket.io-parser": { + "version": "4.2.4", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" } }, - "node_modules/type-is": { - "version": "1.6.18", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" + "socks": { + "version": "2.8.4", + "dev": true, + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", + "socks-proxy-agent": { + "version": "8.0.5", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, - "engines": { - "node": ">= 0.4" + "dependencies": { + "agent-base": { + "version": "7.1.3", + "dev": true + } } }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", + "source-list-map": { + "version": "2.0.1", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "dev": true + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" + "requires": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "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" + } + } } }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", + "source-map-resolve": { + "version": "0.6.0", "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" } }, - "node_modules/typed-array-length": { - "version": "1.0.7", + "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, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "spacetrim": { + "version": "0.11.25", "dev": true }, - "node_modules/typescript": { - "version": "5.8.2", + "sparkles": { + "version": "2.1.0", + "dev": true + }, + "spdx-correct": { + "version": "3.2.0", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/typescript-compare": { - "version": "0.0.2", - "license": "MIT", - "dependencies": { - "typescript-logic": "^0.0.0" - } + "spdx-exceptions": { + "version": "2.5.0", + "dev": true }, - "node_modules/typescript-eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", - "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", + "spdx-expression-parse": { + "version": "3.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.39.0", - "@typescript-eslint/parser": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/utils": "8.39.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/typescript-logic": { - "version": "0.0.0", - "license": "MIT" + "spdx-license-ids": { + "version": "3.0.18", + "dev": true }, - "node_modules/typescript-tuple": { - "version": "2.2.1", - "license": "MIT", - "dependencies": { - "typescript-compare": "^0.0.2" - } + "split2": { + "version": "4.2.0", + "dev": true }, - "node_modules/ua-parser-js": { - "version": "0.7.38", - "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" - } - ], - "license": "MIT", - "engines": { - "node": "*" - } + "sprintf-js": { + "version": "1.0.3" }, - "node_modules/uglify-js": { - "version": "3.18.0", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } + "stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true }, - "node_modules/unc-path-regex": { - "version": "0.1.2", + "stack-utils": { + "version": "2.0.6", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "dev": true + } } }, - "node_modules/undertaker": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bach": "^2.0.1", - "fast-levenshtein": "^3.0.0", - "last-run": "^2.0.0", - "undertaker-registry": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" + "statuses": { + "version": "2.0.1" + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" } }, - "node_modules/undertaker-registry": { - "version": "2.0.0", + "stream-buffers": { + "version": "3.0.2", + "dev": true + }, + "stream-composer": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" + "requires": { + "streamx": "^2.13.2" } }, - "node_modules/undertaker/node_modules/fast-levenshtein": { + "stream-exhaust": { + "version": "1.0.2", + "dev": true + }, + "stream-shift": { + "version": "1.0.3", + "dev": true + }, + "streamfilter": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", + "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", "dev": true, - "license": "MIT", + "requires": { + "readable-stream": "^3.0.6" + }, "dependencies": { - "fastest-levenshtein": "^1.0.7" + "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" + } + } } }, - "node_modules/undici": { - "version": "6.21.3", + "streamroller": { + "version": "3.1.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "dev": true + } } }, - "node_modules/undici-types": { - "version": "5.26.5", - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=4" + "streamx": { + "version": "2.22.0", + "dev": true, + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "strict-event-emitter": { + "version": "0.1.0", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "requires": { + "safe-buffer": "~5.1.0" }, - "engines": { - "node": ">=4" + "dependencies": { + "safe-buffer": { + "version": "5.1.2" + } } }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">=4" - } + "string-template": { + "version": "0.2.1", + "dev": true }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "license": "MIT", - "engines": { - "node": ">=4" + "string-width": { + "version": "4.2.3", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "string-width-cjs": { + "version": "npm:string-width@4.2.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, - "node_modules/universalify": { - "version": "2.0.1", + "string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" } }, - "node_modules/unpipe": { + "string.prototype.repeat": { "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "string.prototype.trim": { + "version": "1.2.10", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", + "string.prototype.trimstart": { + "version": "1.0.8", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, - "node_modules/url": { - "version": "0.11.3", + "strip-ansi": { + "version": "7.1.0", "dev": true, - "license": "MIT", + "requires": { + "ansi-regex": "^6.0.1" + }, "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.2" + "ansi-regex": { + "version": "6.0.1", + "dev": true + } } }, - "node_modules/url-parse": { - "version": "1.5.10", + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "requires": { + "ansi-regex": "^5.0.1" } }, - "node_modules/url-toolkit": { - "version": "2.2.5", - "dev": true, - "license": "Apache-2.0" + "strip-bom": { + "version": "3.0.0", + "dev": true + }, + "strip-bom-string": { + "version": "1.0.0", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "dev": true + }, + "strip-final-newline": { + "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": { + "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 + }, + "strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "dev": true }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", + "supports-color": { + "version": "5.5.0", "dev": true, - "license": "MIT" + "requires": { + "has-flag": "^3.0.0" + } }, - "node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "dev": true, - "license": "MIT" + "supports-preserve-symlinks-flag": { + "version": "1.0.0" }, - "node_modules/userhome": { - "version": "1.0.0", + "sver": { + "version": "1.8.4", "dev": true, - "engines": { - "node": ">= 0.8.0" + "requires": { + "semver": "^6.3.0" } }, - "node_modules/util": { - "version": "0.12.5", + "synckit": { + "version": "0.9.2", "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "requires": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" + "tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true }, - "node_modules/utils-merge": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" + "tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "dev": true, + "requires": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "node_modules/uuid": { - "version": "9.0.1", + "tar-stream": { + "version": "3.1.7", "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/v8flags": { - "version": "4.0.1", + "teex": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" + "requires": { + "streamx": "^2.12.5" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", + "ternary-stream": { + "version": "3.0.0", "dev": true, - "license": "Apache-2.0", + "requires": { + "duplexify": "^4.1.1", + "fork-stream": "^0.0.4", + "merge-stream": "^2.0.0", + "through2": "^3.0.1" + }, "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "through2": { + "version": "3.0.2", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, - "node_modules/value-or-function": { - "version": "4.0.0", + "terser": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.45.0.tgz", + "integrity": "sha512-hQ9c+JZEnMug8eqzuU48sCeq95f00lLDAaJ5gWhRkFXsfy3+SUkZXiF/Z66ZO6EomSmgqXnkhVrWXKaQ8K41Ug==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" } }, - "node_modules/vary": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" } }, - "node_modules/video.js": { - "version": "7.21.7", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.7.tgz", - "integrity": "sha512-T2s3WFAht7Zjr2OSJamND9x9Dn2O+Z5WuHGdh8jI5SYh5mkMdVTQ7vSRmA5PYpjXJ2ycch6jpMjkJEIEU2xxqw==", + "test-exclude": { + "version": "6.0.0", "dev": true, - "license": "Apache-2.0", + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "2.16.3", - "@videojs/vhs-utils": "^3.0.4", - "@videojs/xhr": "2.6.0", - "aes-decrypter": "3.1.3", - "global": "^4.4.0", - "keycode": "^2.2.0", - "m3u8-parser": "4.8.0", - "mpd-parser": "0.22.1", - "mux.js": "6.0.1", - "safe-json-parse": "4.0.0", - "videojs-font": "3.2.0", - "videojs-vtt.js": "^0.15.5" + "glob": { + "version": "7.2.3", + "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" + } + } } }, - "node_modules/video.js/node_modules/safe-json-parse": { - "version": "4.0.0", + "text-decoder": { + "version": "1.1.0", "dev": true, - "dependencies": { - "rust-result": "^1.0.0" + "requires": { + "b4a": "^1.6.4" } }, - "node_modules/videojs-contrib-ads": { - "version": "6.9.0", + "textextensions": { + "version": "3.3.0", + "dev": true + }, + "through": { + "version": "2.3.8", + "dev": true + }, + "through2": { + "version": "4.0.2", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "global": "^4.3.2", - "video.js": "^6 || ^7" + "requires": { + "readable-stream": "3" }, - "engines": { - "node": ">=8", - "npm": ">=5" + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, - "node_modules/videojs-font": { - "version": "3.2.0", + "time-stamp": { + "version": "1.1.0", + "dev": true + }, + "timers-ext": { + "version": "0.1.8", "dev": true, - "license": "Apache-2.0" + "requires": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + } }, - "node_modules/videojs-ima": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.4.0.tgz", - "integrity": "sha512-pP93nNmsjz+BgRIQ3KnQjiB3hgxsHGLweU+23Aq656C9N632t//4gbrnbDBa3XLosBNXrK4uKxuBTFi/6drKRQ==", + "tiny-hashes": { + "version": "1.0.1" + }, + "tiny-lr": { + "version": "1.1.1", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@hapi/cryptiles": "^5.1.0", - "can-autoplay": "^3.0.2", - "extend": ">=3.0.2", - "videojs-contrib-ads": "^6.9.0 || ^7" - }, - "engines": { - "node": ">=0.8.0" + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" }, - "peerDependencies": { - "video.js": "^5.19.2 || ^6 || ^7 || ^8" + "dependencies": { + "debug": { + "version": "3.2.7", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "node_modules/videojs-playlist": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.2.0.tgz", - "integrity": "sha512-Kyx6C5r7zmj6y97RrIlyji8JUEt0kUEfVyB4P6VMyEFVyCGlOlzlgPw2verznBp4uDfjVPPuAJKvNJ7x9O5NJw==", + "tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "global": "^4.3.2", - "video.js": "^6 || ^7 || ^8" + "requires": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, - "engines": { - "node": ">=4.4.0" + "dependencies": { + "fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } } }, - "node_modules/videojs-vtt.js": { - "version": "0.15.5", + "tinyrainbow": { + "version": "1.2.0", + "dev": true + }, + "tmp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", + "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", + "dev": true + }, + "to-absolute-glob": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-3.0.0.tgz", + "integrity": "sha512-loO/XEWTRqpfcpI7+Jr2RR2Umaaozx1t6OSVWtMi0oy5F/Fxg3IC+D/TToDnxyAGs7uZBGT/6XmyDUxgsObJXA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "global": "^4.3.1" + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" } }, - "node_modules/vinyl": { - "version": "2.2.1", + "to-regex-range": { + "version": "5.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" + "requires": { + "is-number": "^7.0.0" + } + }, + "to-through": { + "version": "3.0.0", + "dev": true, + "requires": { + "streamx": "^2.12.5" } }, - "node_modules/vinyl-bufferstream": { - "version": "1.0.1", + "toidentifier": { + "version": "1.0.1" + }, + "totalist": { + "version": "3.0.1", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "triple-beam": { + "version": "1.4.1", + "dev": true + }, + "tryit": { + "version": "1.0.3" + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + }, + "ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "dev": true, + "requires": { + "picomatch": "^4.0.2" + }, "dependencies": { - "bufferstreams": "1.0.1" + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } } }, - "node_modules/vinyl-contents": { - "version": "2.0.0", + "tsconfig-paths": { + "version": "3.15.0", "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^5.0.0", - "vinyl": "^3.0.0" + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" }, - "engines": { - "node": ">=10.13.0" + "dependencies": { + "json5": { + "version": "1.0.2", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } } }, - "node_modules/vinyl-contents/node_modules/vinyl": { - "version": "3.0.1", + "tslib": { + "version": "2.8.1", + "dev": true + }, + "tsx": { + "version": "4.19.3", "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "esbuild": "~0.25.0", + "fsevents": "~2.3.3", + "get-tsconfig": "^4.7.5" } }, - "node_modules/vinyl-fs": { - "version": "4.0.2", + "type": { + "version": "2.7.3", + "dev": true + }, + "type-check": { + "version": "0.4.0", "dev": true, - "license": "MIT", - "dependencies": { - "fs-mkdirp-stream": "^2.0.1", - "glob-stream": "^8.0.3", - "graceful-fs": "^4.2.11", - "iconv-lite": "^0.6.3", - "is-valid-glob": "^1.0.0", - "lead": "^4.0.0", - "normalize-path": "3.0.0", - "resolve-options": "^2.0.0", - "stream-composer": "^1.0.2", - "streamx": "^2.14.0", - "to-through": "^3.0.0", - "value-or-function": "^4.0.0", - "vinyl": "^3.0.1", - "vinyl-sourcemap": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "prelude-ls": "^1.2.1" } }, - "node_modules/vinyl-fs/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "type-detect": { + "version": "4.0.8", + "dev": true + }, + "type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" } }, - "node_modules/vinyl-fs/node_modules/vinyl": { - "version": "3.0.1", + "typed-array-buffer": { + "version": "1.0.3", "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" } }, - "node_modules/vinyl-sourcemap": { - "version": "2.0.0", + "typed-array-byte-length": { + "version": "1.0.3", "dev": true, - "license": "MIT", - "dependencies": { - "convert-source-map": "^2.0.0", - "graceful-fs": "^4.2.10", - "now-and-later": "^3.0.0", - "streamx": "^2.12.5", - "vinyl": "^3.0.0", - "vinyl-contents": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" } }, - "node_modules/vinyl-sourcemap/node_modules/vinyl": { - "version": "3.0.1", + "typed-array-byte-offset": { + "version": "1.0.4", "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" } }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "license": "ISC", - "dependencies": { - "source-map": "^0.5.1" + "typed-array-length": { + "version": "1.0.7", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" } }, - "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { - "version": "0.5.7", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, + "typescript": { + "version": "5.8.2", + "dev": true + }, + "typescript-compare": { + "version": "0.0.2", + "requires": { + "typescript-logic": "^0.0.0" } }, - "node_modules/vinyl/node_modules/replace-ext": { - "version": "1.0.1", + "typescript-eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", + "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" + "requires": { + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0" } }, - "node_modules/void-elements": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "typescript-logic": { + "version": "0.0.0" + }, + "typescript-tuple": { + "version": "2.2.1", + "requires": { + "typescript-compare": "^0.0.2" } }, - "node_modules/wait-port": { - "version": "1.1.0", + "ua-parser-js": { + "version": "0.7.38", + "dev": true + }, + "uglify-js": { + "version": "3.18.0", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "commander": "^9.3.0", - "debug": "^4.3.4" - }, - "bin": { - "wait-port": "bin/wait-port.js" - }, - "engines": { - "node": ">=10" - } + "optional": true }, - "node_modules/wait-port/node_modules/ansi-styles": { - "version": "4.3.0", + "unbox-primitive": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "requires": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" } }, - "node_modules/wait-port/node_modules/chalk": { - "version": "4.1.2", + "unc-path-regex": { + "version": "0.1.2", + "dev": true + }, + "undertaker": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "requires": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "dependencies": { + "fast-levenshtein": { + "version": "3.0.0", + "dev": true, + "requires": { + "fastest-levenshtein": "^1.0.7" + } + } } }, - "node_modules/wait-port/node_modules/color-convert": { + "undertaker-registry": { + "version": "2.0.0", + "dev": true + }, + "undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==" + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" } }, - "node_modules/wait-port/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" + "unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==" }, - "node_modules/wait-port/node_modules/commander": { - "version": "9.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } + "unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==" }, - "node_modules/wait-port/node_modules/has-flag": { - "version": "4.0.0", + "unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true + }, + "universalify": { + "version": "2.0.1", + "dev": true + }, + "unpipe": { + "version": "1.0.0" + }, + "unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1", + "napi-postinstall": "^0.3.0" } }, - "node_modules/wait-port/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "uri-js": { + "version": "4.4.1", "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "punycode": "^2.1.0" } }, - "node_modules/wcwidth": { - "version": "1.0.1", + "url": { + "version": "0.11.3", "dev": true, - "license": "MIT", - "optional": true, + "requires": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + }, "dependencies": { - "defaults": "^1.0.3" + "punycode": { + "version": "1.4.1", + "dev": true + } } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", + "url-parse": { + "version": "1.5.10", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, - "node_modules/webdriver": { - "version": "9.19.2", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.2.tgz", - "integrity": "sha512-kw6dSwNzimU8/CkGVlM36pqWHZ7BhCwV4/d8fu6rpIYGeQbPwcNc4M90TfJuzYMA7Au3NdrwT/EVQgVLQ9Ju8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0", - "@types/ws": "^8.5.3", - "@wdio/config": "9.19.2", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/types": "9.19.2", - "@wdio/utils": "9.19.2", - "deepmerge-ts": "^7.0.3", - "https-proxy-agent": "^7.0.6", - "undici": "^6.21.3", - "ws": "^8.8.0" - }, - "engines": { - "node": ">=18.20.0" - } + "url-toolkit": { + "version": "2.2.5", + "dev": true }, - "node_modules/webdriver/node_modules/@wdio/config": { - "version": "9.19.2", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.2.tgz", - "integrity": "sha512-OVCzPQxav0QDk5rktQ6LYARZ5ueUuJXIqTXUpS3A9Jt6PF+ZUI5sbO/y+z+qHQXqDq+LkscmFsmkzgnoHzHcfg==", + "urlpattern-polyfill": { + "version": "10.0.0", + "dev": true + }, + "userhome": { + "version": "1.0.0", + "dev": true + }, + "util": { + "version": "0.12.5", "dev": true, - "license": "MIT", - "dependencies": { - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.2", - "@wdio/utils": "9.19.2", - "deepmerge-ts": "^7.0.3", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" - }, - "engines": { - "node": ">=18.20.0" + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" } }, - "node_modules/webdriver/node_modules/@wdio/logger": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", - "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "util-deprecate": { + "version": "1.0.2" + }, + "utils-merge": { + "version": "1.0.1" + }, + "uuid": { + "version": "9.0.1", + "dev": true + }, + "v8flags": { + "version": "4.0.1", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "safe-regex2": "^5.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18.20.0" + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/webdriver/node_modules/@wdio/protocols": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", - "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", - "dev": true, - "license": "MIT" + "value-or-function": { + "version": "4.0.0", + "dev": true }, - "node_modules/webdriver/node_modules/@wdio/types": { - "version": "9.19.2", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.2.tgz", - "integrity": "sha512-fBI7ljL+YcPXSXUhdk2+zVuz7IYP1aDMTq1eVmMme9GY0y67t0dCNPOt6xkCAEdL5dOcV6D2L1r6Cf/M2ifTvQ==", + "vary": { + "version": "1.1.2" + }, + "video.js": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.7.tgz", + "integrity": "sha512-T2s3WFAht7Zjr2OSJamND9x9Dn2O+Z5WuHGdh8jI5SYh5mkMdVTQ7vSRmA5PYpjXJ2ycch6jpMjkJEIEU2xxqw==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0" + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "2.16.3", + "@videojs/vhs-utils": "^3.0.4", + "@videojs/xhr": "2.6.0", + "aes-decrypter": "3.1.3", + "global": "^4.4.0", + "keycode": "^2.2.0", + "m3u8-parser": "4.8.0", + "mpd-parser": "0.22.1", + "mux.js": "6.0.1", + "safe-json-parse": "4.0.0", + "videojs-font": "3.2.0", + "videojs-vtt.js": "^0.15.5" }, - "engines": { - "node": ">=18.20.0" + "dependencies": { + "safe-json-parse": { + "version": "4.0.0", + "dev": true, + "requires": { + "rust-result": "^1.0.0" + } + } } }, - "node_modules/webdriver/node_modules/@wdio/utils": { - "version": "9.19.2", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.2.tgz", - "integrity": "sha512-caimJiTsxDUfXn/gRAzcYTO3RydSl7XzD+QpjfWZYJjzr8a2XfNnj+Vdmr8gG4BSkiVHirW9mFCZeQp2eTD7rA==", + "videojs-contrib-ads": { + "version": "6.9.0", "dev": true, - "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.2", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.2", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "mitt": "^3.0.1", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" - }, - "engines": { - "node": ">=18.20.0" + "requires": { + "global": "^4.3.2", + "video.js": "^6 || ^7" } }, - "node_modules/webdriver/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "videojs-font": { + "version": "3.2.0", + "dev": true + }, + "videojs-ima": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.4.0.tgz", + "integrity": "sha512-pP93nNmsjz+BgRIQ3KnQjiB3hgxsHGLweU+23Aq656C9N632t//4gbrnbDBa3XLosBNXrK4uKxuBTFi/6drKRQ==", + "dev": true, + "requires": { + "@hapi/cryptiles": "^5.1.0", + "can-autoplay": "^3.0.2", + "extend": ">=3.0.2", + "videojs-contrib-ads": "^6.9.0 || ^7" } }, - "node_modules/webdriver/node_modules/chalk": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", - "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "videojs-playlist": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.2.0.tgz", + "integrity": "sha512-Kyx6C5r7zmj6y97RrIlyji8JUEt0kUEfVyB4P6VMyEFVyCGlOlzlgPw2verznBp4uDfjVPPuAJKvNJ7x9O5NJw==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "global": "^4.3.2", + "video.js": "^6 || ^7 || ^8" } }, - "node_modules/webdriver/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "videojs-vtt.js": { + "version": "0.15.5", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "requires": { + "global": "^4.3.1" } }, - "node_modules/webdriverio": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.1.tgz", - "integrity": "sha512-hpGgK6d9QNi3AaLFWIPQaEMqJhXF048XAIsV5i5mkL0kjghV1opcuhKgbbG+7pcn8JSpiq6mh7o3MDYtapw90w==", + "vinyl": { + "version": "2.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.11.30", - "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.19.1", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/repl": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "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.8.1", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "query-selector-shadow-dom": "^1.0.1", - "resq": "^1.11.0", - "rgb2hex": "0.2.5", - "serialize-error": "^12.0.0", - "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.19.1" - }, - "engines": { - "node": ">=18.20.0" - }, - "peerDependencies": { - "puppeteer-core": ">=22.x || <=24.x" + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" }, - "peerDependenciesMeta": { - "puppeteer-core": { - "optional": true + "dependencies": { + "replace-ext": { + "version": "1.0.1", + "dev": true } } }, - "node_modules/webdriverio/node_modules/@wdio/config": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", - "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "deepmerge-ts": "^7.0.3", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" - }, - "engines": { - "node": ">=18.20.0" + "vinyl-bufferstream": { + "version": "1.0.1", + "requires": { + "bufferstreams": "1.0.1" } }, - "node_modules/webdriverio/node_modules/@wdio/logger": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", - "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "vinyl-contents": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "safe-regex2": "^5.0.0", - "strip-ansi": "^7.1.0" + "requires": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" }, - "engines": { - "node": ">=18.20.0" + "dependencies": { + "vinyl": { + "version": "3.0.1", + "dev": true, + "requires": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + } } }, - "node_modules/webdriverio/node_modules/@wdio/protocols": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", - "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", - "dev": true, - "license": "MIT" - }, - "node_modules/webdriverio/node_modules/@wdio/repl": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", - "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", + "vinyl-fs": { + "version": "4.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0" + "requires": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" }, - "engines": { - "node": ">=18.20.0" + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "vinyl": { + "version": "3.0.1", + "dev": true, + "requires": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + } } }, - "node_modules/webdriverio/node_modules/@wdio/types": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", - "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "vinyl-sourcemap": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0" + "requires": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" }, - "engines": { - "node": ">=18.20.0" + "dependencies": { + "vinyl": { + "version": "3.0.1", + "dev": true, + "requires": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + } } }, - "node_modules/webdriverio/node_modules/@wdio/utils": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", - "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.2", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "mitt": "^3.0.1", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "requires": { + "source-map": "^0.5.1" }, - "engines": { - "node": ">=18.20.0" + "dependencies": { + "source-map": { + "version": "0.5.7" + } } }, - "node_modules/webdriverio/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "void-elements": { + "version": "2.0.1", + "dev": true + }, + "wait-port": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "requires": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "commander": { + "version": "9.5.0", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/webdriverio/node_modules/chalk": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", - "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" } }, - "node_modules/webdriverio/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "wcwidth": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "optional": true, + "requires": { + "defaults": "^1.0.3" } }, - "node_modules/webdriverio/node_modules/webdriver": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.1.tgz", - "integrity": "sha512-cvccIZ3QaUZxxrA81a3rqqgxKt6VzVrZupMc+eX9J40qfGrV3NtdLb/m4AA1PmeTPGN5O3/4KrzDpnVZM4WUnA==", + "web-streams-polyfill": { + "version": "4.0.0-beta.3", + "dev": true + }, + "webdriver": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.2.tgz", + "integrity": "sha512-kw6dSwNzimU8/CkGVlM36pqWHZ7BhCwV4/d8fu6rpIYGeQbPwcNc4M90TfJuzYMA7Au3NdrwT/EVQgVLQ9Ju8Q==", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.19.1", + "@wdio/config": "9.19.2", "@wdio/logger": "9.18.0", "@wdio/protocols": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", "ws": "^8.8.0" }, - "engines": { - "node": ">=18.20.0" + "dependencies": { + "@wdio/config": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.2.tgz", + "integrity": "sha512-OVCzPQxav0QDk5rktQ6LYARZ5ueUuJXIqTXUpS3A9Jt6PF+ZUI5sbO/y+z+qHQXqDq+LkscmFsmkzgnoHzHcfg==", + "dev": true, + "requires": { + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + } + }, + "@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "dev": true + }, + "@wdio/types": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.2.tgz", + "integrity": "sha512-fBI7ljL+YcPXSXUhdk2+zVuz7IYP1aDMTq1eVmMme9GY0y67t0dCNPOt6xkCAEdL5dOcV6D2L1r6Cf/M2ifTvQ==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.2.tgz", + "integrity": "sha512-caimJiTsxDUfXn/gRAzcYTO3RydSl7XzD+QpjfWZYJjzr8a2XfNnj+Vdmr8gG4BSkiVHirW9mFCZeQp2eTD7rA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, + "chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + } } }, - "node_modules/webpack": { - "version": "5.102.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", - "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", + "webdriverio": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.1.tgz", + "integrity": "sha512-hpGgK6d9QNi3AaLFWIPQaEMqJhXF048XAIsV5i5mkL0kjghV1opcuhKgbbG+7pcn8JSpiq6mh7o3MDYtapw90w==", "dev": true, - "license": "MIT", + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.19.1", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/repl": "9.16.2", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "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.8.1", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^12.0.0", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.19.1" + }, "dependencies": { + "@wdio/config": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", + "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", + "dev": true, + "requires": { + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + } + }, + "@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "dev": true + }, + "@wdio/repl": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", + "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/types": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", + "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, + "chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "webdriver": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.1.tgz", + "integrity": "sha512-cvccIZ3QaUZxxrA81a3rqqgxKt6VzVrZupMc+eX9J40qfGrV3NtdLb/m4AA1PmeTPGN5O3/4KrzDpnVZM4WUnA==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.19.1", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", + "deepmerge-ts": "^7.0.3", + "https-proxy-agent": "^7.0.6", + "undici": "^6.21.3", + "ws": "^8.8.0" + } + } + } + }, + "webpack": { + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "dev": true, + "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", @@ -21058,45 +36264,41 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true + "dependencies": { + "es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true } } }, - "node_modules/webpack-bundle-analyzer": { + "webpack-bundle-analyzer": { "version": "4.10.2", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", @@ -21110,104 +36312,59 @@ "sirv": "^2.0.3", "ws": "^7.3.1" }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.10", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true + "dependencies": { + "commander": { + "version": "7.2.0", + "dev": true }, - "utf-8-validate": { - "optional": true + "escape-string-regexp": { + "version": "4.0.0", + "dev": true + }, + "ws": { + "version": "7.5.10", + "dev": true, + "requires": {} } } }, - "node_modules/webpack-manifest-plugin": { + "webpack-manifest-plugin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.1.tgz", "integrity": "sha512-xTlX7dC3hrASixA2inuWFMz6qHsNi6MT3Uiqw621sJjRTShtpMjbDYhPPZBwWUKdIYKIjSq9em6+uzWayf38aQ==", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "tapable": "^2.0.0", "webpack-sources": "^2.2.0" }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "webpack": "^5.75.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "dev": true, - "license": "MIT", "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" + "webpack-sources": { + "version": "2.3.1", + "dev": true, + "requires": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + } + } } }, - "node_modules/webpack-merge": { + "webpack-merge": { "version": "4.2.2", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "lodash": "^4.17.15" } }, - "node_modules/webpack-sources": { + "webpack-sources": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } + "dev": true }, - "node_modules/webpack-stream": { + "webpack-stream": { "version": "7.0.0", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "fancy-log": "^1.3.3", "lodash.clone": "^4.3.2", "lodash.some": "^4.2.2", @@ -21215,197 +36372,120 @@ "plugin-error": "^1.0.1", "supports-color": "^8.1.1", "through": "^2.3.8", - "vinyl": "^2.2.1" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "webpack": "^5.21.2" - } - }, - "node_modules/webpack-stream/node_modules/ansi-colors": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-stream/node_modules/arr-diff": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-stream/node_modules/arr-union": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-stream/node_modules/extend-shallow": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-stream/node_modules/fancy-log": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/webpack-stream/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-stream/node_modules/plugin-error": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/webpack-stream/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" + "vinyl": "^2.2.1" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "fancy-log": { + "version": "1.3.3", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "plugin-error": { + "version": "1.0.1", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "supports-color": { + "version": "8.1.1", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/webpack/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/websocket-driver": { + "websocket-driver": { "version": "0.7.4", "dev": true, - "license": "Apache-2.0", - "dependencies": { + "requires": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" } }, - "node_modules/websocket-extensions": { + "websocket-extensions": { "version": "0.1.4", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } + "dev": true }, - "node_modules/whatwg-encoding": { + "whatwg-encoding": { "version": "3.1.1", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "iconv-lite": "0.6.3" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "iconv-lite": { + "version": "0.6.3", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, - "node_modules/whatwg-mimetype": { + "whatwg-mimetype": { "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "dev": true }, - "node_modules/which": { + "which": { "version": "5.0.0", "dev": true, - "license": "ISC", - "dependencies": { + "requires": { "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/which-boxed-primitive": { + "which-boxed-primitive": { "version": "1.1.1", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-builtin-type": { + "which-builtin-type": { "version": "1.2.1", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", @@ -21414,436 +36494,267 @@ "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/winston-transport": { - "version": "4.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/workerpool": { - "version": "6.5.1", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "which-collection": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "which-typed-array": { + "version": "1.1.19", "dev": true, - "license": "MIT" + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } }, - "node_modules/wrap-ansi/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==", + "winston-transport": { + "version": "4.7.0", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" }, - "engines": { - "node": ">=8" + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, - "node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" + "word-wrap": { + "version": "1.2.5", + "dev": true }, - "node_modules/ws": { - "version": "8.17.1", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "wordwrap": { + "version": "1.0.0", + "dev": true + }, + "workerpool": { + "version": "6.5.1", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true + "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" + } }, - "utf-8-validate": { - "optional": true + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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 + }, + "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" + } } } }, - "node_modules/xtend": { - "version": "4.0.2", - "license": "MIT", - "engines": { - "node": ">=0.4" + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, - "node_modules/y18n": { + "wrappy": { + "version": "1.0.2", + "dev": true + }, + "ws": { + "version": "8.17.1", + "dev": true, + "requires": {} + }, + "xtend": { + "version": "4.0.2" + }, + "y18n": { "version": "5.0.8", - "license": "ISC", - "engines": { - "node": ">=10" - } + "dev": true }, - "node_modules/yallist": { - "version": "3.1.1", - "license": "ISC" + "yallist": { + "version": "3.1.1" }, - "node_modules/yargs": { + "yargs": { "version": "1.3.3", - "dev": true, - "license": "MIT/X11" + "dev": true }, - "node_modules/yargs-parser": { + "yargs-parser": { "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } + "dev": true }, - "node_modules/yargs-unparser": { + "yargs-unparser": { "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "camelcase": { + "version": "6.3.0", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "dev": true + } } }, - "node_modules/yauzl": { + "yauzl": { "version": "3.1.3", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" - }, - "engines": { - "node": ">=12" } }, - "node_modules/yocto-queue": { + "yocto-queue": { "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "dev": true }, - "node_modules/yoctocolors": { + "yoctocolors": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "dev": true }, - "node_modules/yoctocolors-cjs": { + "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, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "dev": true }, - "node_modules/zip-stream": { + "zip-stream": { "version": "6.0.1", "dev": true, - "license": "MIT", - "dependencies": { + "requires": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/zip-stream/node_modules/buffer": { - "version": "6.0.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "dependencies": { + "buffer": { + "version": "6.0.3", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "readable-stream": { + "version": "4.5.2", + "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" + } }, - { - "type": "consulting", - "url": "https://feross.org/support" + "string_decoder": { + "version": "1.3.0", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "4.5.2", - "dev": true, - "license": "MIT", - "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/zip-stream/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" } }, - "node_modules/zod": { + "zod": { "version": "3.25.67", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } + "dev": true } } } diff --git a/package.json b/package.json index 439d82f77af..c82ac4ff4f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.18.0-pre", + "version": "10.28.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { @@ -53,11 +53,11 @@ "node": ">=20.0.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.16.5", + "@babel/eslint-parser": "^7.28.6", "@babel/plugin-transform-runtime": "^7.27.4", "@babel/register": "^7.28.3", "@chiragrupani/karma-chromium-edge-launcher": "^2.4.1", - "@eslint/compat": "^1.3.1", + "@eslint/compat": "^1.4.1", "@types/google-publisher-tag": "^1.20250210.0", "@wdio/browserstack-service": "^9.19.1", "@wdio/cli": "^9.19.1", @@ -105,6 +105,7 @@ "karma-mocha-reporter": "^2.2.5", "karma-opera-launcher": "^1.0.0", "karma-safari-launcher": "^1.0.0", + "karma-safarinative-launcher": "^1.1.0", "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.4.0", @@ -134,7 +135,7 @@ "videojs-playlist": "^5.2.0", "webdriver": "^9.19.2", "webdriverio": "^9.18.4", - "webpack": "^5.102.1", + "webpack": "^5.103.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.1", "webpack-stream": "^7.0.0", @@ -142,9 +143,8 @@ }, "dependencies": { "@babel/core": "^7.28.4", - "@babel/plugin-transform-runtime": "^7.18.9", - "@babel/preset-env": "^7.27.2", - "@babel/preset-typescript": "^7.26.0", + "@babel/preset-env": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@babel/runtime": "^7.28.3", "core-js": "^3.45.1", "crypto-js": "^4.2.0", @@ -157,13 +157,18 @@ "iab-adcom": "^1.0.6", "iab-native": "^1.0.0", "iab-openrtb": "^1.0.1", - "karma-safarinative-launcher": "^1.1.0", "klona": "^2.0.6", "live-connect-js": "^7.2.0" }, "optionalDependencies": { "fsevents": "^2.3.2" }, + "overrides": { + "gulp-connect": { + "send": "0.19.0" + }, + "send": "0.19.0" + }, "peerDependencies": { "schema-utils": "^4.3.2" } diff --git a/plugins/eslint/approvedLoadExternalScriptPaths.js b/plugins/eslint/approvedLoadExternalScriptPaths.js new file mode 100644 index 00000000000..0eab8545924 --- /dev/null +++ b/plugins/eslint/approvedLoadExternalScriptPaths.js @@ -0,0 +1,48 @@ +// List of exact file paths or folder paths where loadExternalScript is allowed to be used. +// Folder paths (without file extension) allow all files in that folder. +const APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS = [ + // Prebid maintained modules: + 'src/debugging.js', + 'src/Renderer.js', + // RTD modules: + 'modules/aaxBlockmeterRtdProvider.js', + 'modules/adagioRtdProvider.js', + 'modules/adlooxAnalyticsAdapter.js', + 'modules/arcspanRtdProvider.js', + 'modules/airgridRtdProvider.js', + 'modules/browsiRtdProvider.js', + 'modules/brandmetricsRtdProvider.js', + 'modules/cleanioRtdProvider.js', + 'modules/humansecurityMalvDefenseRtdProvider.js', + 'modules/humansecurityRtdProvider.ts', + 'modules/confiantRtdProvider.js', + 'modules/contxtfulRtdProvider.js', + 'modules/hadronRtdProvider.js', + 'modules/mediafilterRtdProvider.js', + 'modules/medianetRtdProvider.js', + 'modules/azerionedgeRtdProvider.js', + 'modules/a1MediaRtdProvider.js', + 'modules/geoedgeRtdProvider.js', + 'modules/qortexRtdProvider.js', + 'modules/dynamicAdBoostRtdProvider.js', + 'modules/51DegreesRtdProvider.js', + 'modules/symitriDapRtdProvider.js', + 'modules/wurflRtdProvider.js', + 'modules/nodalsAiRtdProvider.js', + 'modules/anonymisedRtdProvider.js', + 'modules/optableRtdProvider.js', + 'modules/oftmediaRtdProvider.js', + 'modules/panxoRtdProvider.js', + // UserId Submodules + 'modules/justIdSystem.js', + 'modules/tncIdSystem.js', + 'modules/ftrackIdSystem.js', + 'modules/id5IdSystem.js', + // Test files + '**/*spec.js', + '**/*spec.ts', + '**/test/**/*', +]; + +module.exports = APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS; + diff --git a/src/activities/activities.js b/src/activities/activities.js index 53d73e26c3b..f436222603b 100644 --- a/src/activities/activities.js +++ b/src/activities/activities.js @@ -60,3 +60,8 @@ export const LOAD_EXTERNAL_SCRIPT = 'loadExternalScript'; * accessRequestCredentials: setting withCredentials flag in ajax request config */ export const ACTIVITY_ACCESS_REQUEST_CREDENTIALS = 'accessRequestCredentials'; + +/** + * acceptBid: a bid is about to be accepted. + */ +export const ACTIVITY_ADD_BID_RESPONSE = 'acceptBid'; diff --git a/src/adRendering.ts b/src/adRendering.ts index b35959c75e0..25aba25a59f 100644 --- a/src/adRendering.ts +++ b/src/adRendering.ts @@ -17,12 +17,13 @@ import {auctionManager} from './auctionManager.js'; import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; -import {PbPromise} from './utils/promise.js'; import adapterManager from './adapterManager.js'; import {useMetrics} from './utils/perfMetrics.js'; import {filters} from './targeting.js'; import {EVENT_TYPE_WIN, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTrackers.js'; import type {Bid} from "./bidfactory.ts"; +import {yieldsIf} from "./utils/yield.ts"; +import {PbPromise} from "./utils/promise.ts"; const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON, EXPIRED_RENDER } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; @@ -55,10 +56,13 @@ declare module './events' { } } -export const getBidToRender = hook('sync', function (adId, forRender = true, override = PbPromise.resolve()) { - return override - .then(bid => bid ?? auctionManager.findBidByAdId(adId)) - .catch(() => {}) +/** + * NOTE: this is here to support PAAPI, which is soon to be removed; + * and should *not* be made asynchronous or it breaks `legacyRender` (unyielding) + * rendering logic + */ +export const getBidToRender = hook('sync', function (adId, forRender, cb) { + cb(auctionManager.findBidByAdId(adId)); }) export const markWinningBid = hook('sync', function (bid) { @@ -331,7 +335,12 @@ export function renderIfDeferred(bidResponse) { } } -export function renderAdDirect(doc, adId, options) { +let legacyRender = false; +config.getConfig('auctionOptions', (opts) => { + legacyRender = opts.auctionOptions?.legacyRender ?? false +}); + +export const renderAdDirect = yieldsIf(() => !legacyRender, function renderAdDirect(doc, adId, options) { let bid; function fail(reason, message) { emitAdRenderFail(Object.assign({id: adId, bid}, {reason, message})); @@ -362,20 +371,26 @@ export function renderAdDirect(doc, adId, options) { } function renderFn(adData) { - PbPromise.all([ - getCreativeRenderer(bid), - waitForDocumentReady(doc) - ]).then(([render]) => render(adData, { - sendMessage: (type, data) => messageHandler(type, data, bid), - mkFrame: createIframe, - }, doc.defaultView)) - .then( - () => emitAdRenderSucceeded({doc, bid, id: bid.adId}), - (e) => { - fail(e?.reason || AD_RENDER_FAILED_REASON.EXCEPTION, e?.message) - e?.stack && logError(e); - } - ); + if (adData.ad && legacyRender) { + doc.write(adData.ad); + doc.close(); + emitAdRenderSucceeded({doc, bid, id: bid.adId}); + } else { + PbPromise.all([ + getCreativeRenderer(bid), + waitForDocumentReady(doc) + ]).then(([render]) => render(adData, { + sendMessage: (type, data) => messageHandler(type, data, bid), + mkFrame: createIframe, + }, doc.defaultView)) + .then( + () => emitAdRenderSucceeded({doc, bid, id: bid.adId}), + (e) => { + fail(e?.reason || AD_RENDER_FAILED_REASON.EXCEPTION, e?.message) + e?.stack && logError(e); + } + ); + } // TODO: this is almost certainly the wrong way to do this const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); insertElement(creativeComment, doc, 'html'); @@ -384,7 +399,7 @@ export function renderAdDirect(doc, adId, options) { if (!adId || !doc) { fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); } else { - getBidToRender(adId).then(bidResponse => { + getBidToRender(adId, true, (bidResponse) => { bid = bidResponse; handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse, doc}); }); @@ -392,7 +407,7 @@ export function renderAdDirect(doc, adId, options) { } catch (e) { fail(EXCEPTION, e.message); } -} +}); /** * Insert an invisible, named iframe that can be used by creatives to locate the window Prebid is running in diff --git a/src/adapterManager.ts b/src/adapterManager.ts index d19a43fcf03..7c0faf40d1d 100644 --- a/src/adapterManager.ts +++ b/src/adapterManager.ts @@ -93,7 +93,7 @@ config.getConfig('s2sConfig', config => { } }); -const activityParams = activityParamsBuilder((alias) => adapterManager.resolveAlias(alias)); +export const activityParams = activityParamsBuilder((alias) => adapterManager.resolveAlias(alias)); function getConfigName(s2sConfig) { // According to our docs, "module" bid (stored impressions) @@ -195,6 +195,7 @@ export interface BaseBidderRequest { gdprConsent?: ReturnType; uspConsent?: ReturnType; gppConsent?: ReturnType; + alwaysHasCapacity?: boolean; } export interface S2SBidderRequest extends BaseBidderRequest { @@ -505,18 +506,78 @@ const adapterManager = { .filter(uniques) .forEach(incrementAuctionsCounter); + const ortb2 = ortb2Fragments.global || {}; + const bidderOrtb2 = ortb2Fragments.bidder || {}; + + const getTid = tidFactory(); + + const getCacheKey = (bidderCode: BidderCode, s2sActivityParams?): string => { + const s2sName = s2sActivityParams != null ? s2sActivityParams[ACTIVITY_PARAM_S2S_NAME] : ''; + return s2sName ? `${bidderCode}:${s2sName}` : `${bidderCode}:`; + }; + + const mergeBidderFpd = (() => { + const fpdCache: any = {}; + return function(auctionId: string, bidderCode: BidderCode, s2sActivityParams?) { + const cacheKey = getCacheKey(bidderCode, s2sActivityParams); + const redact = dep.redact( + s2sActivityParams != null + ? s2sActivityParams + : activityParams(MODULE_TYPE_BIDDER, bidderCode) + ); + if (fpdCache[cacheKey] !== undefined) { + return [fpdCache[cacheKey], redact]; + } + const [tid, tidSource] = getTid(bidderCode, auctionId, bidderOrtb2[bidderCode]?.source?.tid ?? ortb2.source?.tid); + const fpd = Object.freeze(redact.ortb2(mergeDeep( + {}, + ortb2, + bidderOrtb2[bidderCode], + { + source: { + tid, + ext: {tidSource} + } + } + ))); + fpdCache[cacheKey] = fpd; + return [fpd, redact]; + } + })(); + + let {[PARTITIONS.CLIENT]: clientBidders, [PARTITIONS.SERVER]: serverBidders} = partitionBidders(adUnits, _s2sConfigs); + const allowedBidders = new Set(); + adUnits.forEach(au => { if (!isPlainObject(au.mediaTypes)) { au.mediaTypes = {}; } + // filter out bidders that cannot participate in the auction - au.bids = au.bids.filter((bid) => !bid.bidder || dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_BIDDER, bid.bidder))) + au.bids = au.bids.filter((bid) => { + if (!bid.bidder) { + return true; + } + const [ortb2] = mergeBidderFpd(auctionId, bid.bidder); + const isS2S = serverBidders.includes(bid.bidder) && !clientBidders.includes(bid.bidder); + return dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_BIDDER, bid.bidder, { + bid, + ortb2, + adUnit: au, + auctionId, + isS2S + })); + }); + au.bids.forEach(bid => { + allowedBidders.add(bid.bidder); + }); incrementRequestsCounter(au.code); }); - adUnits = setupAdUnitMediaTypes(adUnits, labels); + clientBidders = clientBidders.filter(bidder => allowedBidders.has(bidder)); + serverBidders = serverBidders.filter(bidder => allowedBidders.has(bidder)); - let {[PARTITIONS.CLIENT]: clientBidders, [PARTITIONS.SERVER]: serverBidders} = partitionBidders(adUnits, _s2sConfigs); + adUnits = setupAdUnitMediaTypes(adUnits, labels); if (config.getConfig('bidderSequence') === RANDOM) { clientBidders = shuffle(clientBidders); @@ -525,29 +586,8 @@ const adapterManager = { const bidRequests: BidderRequest[] = []; - const ortb2 = ortb2Fragments.global || {}; - const bidderOrtb2 = ortb2Fragments.bidder || {}; - - const getTid = tidFactory(); - function addOrtb2>(bidderRequest: Partial, s2sActivityParams?): T { - const redact = dep.redact( - s2sActivityParams != null - ? s2sActivityParams - : activityParams(MODULE_TYPE_BIDDER, bidderRequest.bidderCode) - ); - const [tid, tidSource] = getTid(bidderRequest.bidderCode, bidderRequest.auctionId, bidderOrtb2[bidderRequest.bidderCode]?.source?.tid ?? ortb2.source?.tid); - const fpd = Object.freeze(redact.ortb2(mergeDeep( - {}, - ortb2, - bidderOrtb2[bidderRequest.bidderCode], - { - source: { - tid, - ext: {tidSource} - } - } - ))); + const [fpd, redact] = mergeBidderFpd(bidderRequest.auctionId, bidderRequest.bidderCode, s2sActivityParams); bidderRequest.ortb2 = fpd; bidderRequest.bids = bidderRequest.bids.map((bid) => { bid.ortb2 = fpd; @@ -597,6 +637,7 @@ const adapterManager = { src: S2S.SRC, refererInfo, metrics, + alwaysHasCapacity: s2sConfig.alwaysHasCapacity, }, s2sParams); if (bidderRequest.bids.length !== 0) { bidRequests.push(bidderRequest); @@ -626,6 +667,7 @@ const adapterManager = { const bidderRequestId = generateUUID(); const pageViewId = getPageViewIdForBidder(bidderCode); const metrics = auctionMetrics.fork(); + const adapter = _bidderRegistry[bidderCode]; const bidderRequest = addOrtb2({ bidderCode, auctionId, @@ -644,8 +686,9 @@ const adapterManager = { timeout: cbTimeout, refererInfo, metrics, + src: 'client', + alwaysHasCapacity: adapter?.getSpec?.().alwaysHasCapacity, }); - const adapter = _bidderRegistry[bidderCode]; if (!adapter) { logError(`Trying to make a request for bidder that does not exist: ${bidderCode}`); } diff --git a/src/adapters/bidderFactory.ts b/src/adapters/bidderFactory.ts index bbd42934057..60f21964f88 100644 --- a/src/adapters/bidderFactory.ts +++ b/src/adapters/bidderFactory.ts @@ -157,6 +157,7 @@ export interface BidderSpec extends StorageDisclosure uspConsent: null | ConsentData[typeof CONSENT_USP], gppConsent: null | ConsentData[typeof CONSENT_GPP] ) => ({ type: SyncType, url: string })[]; + alwaysHasCapacity?: boolean; } export type BidAdapter = { diff --git a/src/adloader.js b/src/adloader.js index 098b78c211b..71f6698141e 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -5,45 +5,6 @@ import { isActivityAllowed } from './activities/rules.js'; import { insertElement, logError, logWarn, setScriptAttributes } from './utils.js'; const _requestCache = new WeakMap(); -// The below list contains modules or vendors whom Prebid allows to load external JS. -const _approvedLoadExternalJSList = [ - // Prebid maintained modules: - 'debugging', - 'outstream', - // RTD modules: - 'aaxBlockmeter', - 'adagio', - 'adloox', - 'arcspan', - 'airgrid', - 'browsi', - 'brandmetrics', - 'clean.io', - 'humansecurityMalvDefense', - 'humansecurity', - 'confiant', - 'contxtful', - 'hadron', - 'mediafilter', - 'medianet', - 'azerionedge', - 'a1Media', - 'geoedge', - 'qortex', - 'dynamicAdBoost', - '51Degrees', - 'symitridap', - 'wurfl', - 'nodalsAi', - 'anonymised', - 'optable', - 'oftmedia', - // UserId Submodules - 'justtag', - 'tncId', - 'ftrackId', - 'id5', -]; /** * Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy @@ -64,10 +25,7 @@ export function loadExternalScript(url, moduleType, moduleCode, callback, doc, a logError('cannot load external script without url and moduleCode'); return; } - if (!_approvedLoadExternalJSList.includes(moduleCode)) { - logError(`${moduleCode} not whitelisted for loading external JavaScript`); - return; - } + if (!doc) { doc = document; // provide a "valid" key for the WeakMap } diff --git a/src/auction.ts b/src/auction.ts index 4fe57c74223..a6912be69be 100644 --- a/src/auction.ts +++ b/src/auction.ts @@ -22,7 +22,7 @@ import {AUDIO, VIDEO} from './mediaTypes.js'; import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; -import adapterManager, {type BidderRequest, type BidRequest} from './adapterManager.js'; +import adapterManager, {activityParams, type BidderRequest, type BidRequest} from './adapterManager.js'; import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; import {defer, PbPromise} from './utils/promise.js'; import {type Metrics, useMetrics} from './utils/perfMetrics.js'; @@ -36,6 +36,9 @@ import type {TargetingMap} from "./targeting.ts"; import type {AdUnit} from "./adUnits.ts"; import type {MediaType} from "./mediaTypes.ts"; import type {VideoContext} from "./video.ts"; +import { isActivityAllowed } from './activities/rules.js'; +import { ACTIVITY_ADD_BID_RESPONSE } from './activities/activities.js'; +import { MODULE_TYPE_BIDDER } from './activities/modules.ts'; const { syncUsers } = userSync; @@ -135,6 +138,15 @@ export interface AuctionOptionsConfig { * When true, prevent bids from being rendered if TTL is reached. Default is false. */ suppressExpiredRender?: boolean; + + /** + * If true, use legacy rendering logic. + * + * Since Prebid 10.12, `pbjs.renderAd` wraps creatives in an additional iframe. This can cause problems for some creatives + * that try to reach the top window and do not expect to find the extra iframe. You may set `legacyRender: true` to revert + * to pre-10.12 rendering logic. + */ + legacyRender?: boolean; } export interface PriceBucketConfig { @@ -243,7 +255,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a done.resolve(); events.emit(EVENTS.AUCTION_END, getProperties()); - bidsBackCallback(_adUnits, function () { + bidsBackCallback(_adUnits, auctionId, function () { try { if (_callback != null) { const bids = _bidsReceived.toArray() @@ -366,6 +378,12 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let requests = 1; const source = (typeof bidRequest.src !== 'undefined' && bidRequest.src === S2S.SRC) ? 's2s' : bidRequest.bidderCode; + + // if the bidder has alwaysHasCapacity flag set and forceMaxRequestsPerOrigin is false, don't check capacity + if (bidRequest.alwaysHasCapacity && !config.getConfig('forceMaxRequestsPerOrigin')) { + return false; + } + // if we have no previous info on this source just let them through if (sourceInfo[source]) { if (sourceInfo[source].SRA === false) { @@ -454,7 +472,13 @@ declare module './hook' { */ export const addBidResponse = ignoreCallbackArg(hook('async', function(adUnitCode: string, bid: Partial, reject: (reason: (typeof REJECTION_REASON)[keyof typeof REJECTION_REASON]) => void): void { if (!isValidPrice(bid)) { - reject(REJECTION_REASON.PRICE_TOO_HIGH) + reject(REJECTION_REASON.PRICE_TOO_HIGH); + } else if (!isActivityAllowed(ACTIVITY_ADD_BID_RESPONSE, activityParams(MODULE_TYPE_BIDDER, bid.bidder || bid.bidderCode, { + bid, + ortb2: auctionManager.index.getOrtb2(bid), + adUnit: auctionManager.index.getAdUnit(bid), + }))) { + reject(REJECTION_REASON.BIDDER_DISALLOWED); } else { this.dispatch.call(null, adUnitCode, bid); } @@ -472,7 +496,7 @@ export const addBidderRequests = hook('sync', function(bidderRequests) { this.dispatch.call(this.context, bidderRequests); }, 'addBidderRequests'); -export const bidsBackCallback = hook('async', function (adUnits, callback) { +export const bidsBackCallback = hook('async', function (adUnits, auctionId, callback) { if (callback) { callback(); } @@ -548,6 +572,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM bidderRequest.bids.forEach(bid => { if (!bidResponseMap[bid.bidId]) { + addBidTimingProperties(bid); auctionInstance.addNoBid(bid); events.emit(EVENTS.NO_BID, bid); } @@ -704,17 +729,30 @@ declare module './bidfactory' { adserverTargeting: BaseBidResponse['adserverTargeting']; } } + /** - * Augment `bidResponse` with properties that are common across all bids - including rejected bids. + * Add timing properties to a bid response */ -function addCommonResponseProperties(bidResponse: Partial, adUnitCode: string, {index = auctionManager.index} = {}) { +function addBidTimingProperties(bidResponse: Partial, {index = auctionManager.index} = {}) { const bidderRequest = index.getBidderRequest(bidResponse); - const adUnit = index.getAdUnit(bidResponse); const start = (bidderRequest && bidderRequest.start) || bidResponse.requestTimestamp; Object.assign(bidResponse, { responseTimestamp: bidResponse.responseTimestamp || timestamp(), requestTimestamp: bidResponse.requestTimestamp || start, + }); + bidResponse.timeToRespond = bidResponse.responseTimestamp - bidResponse.requestTimestamp; +} + +/** + * Augment `bidResponse` with properties that are common across all bids - including rejected bids. + */ +function addCommonResponseProperties(bidResponse: Partial, adUnitCode: string, {index = auctionManager.index} = {}) { + const adUnit = index.getAdUnit(bidResponse); + + addBidTimingProperties(bidResponse, {index}) + + Object.assign(bidResponse, { cpm: parseFloat(bidResponse.cpm) || 0, bidder: bidResponse.bidder || bidResponse.bidderCode, adUnitCode @@ -723,8 +761,6 @@ function addCommonResponseProperties(bidResponse: Partial, adUnitCode: stri if (adUnit?.ttlBuffer != null) { bidResponse.ttlBuffer = adUnit.ttlBuffer; } - - bidResponse.timeToRespond = bidResponse.responseTimestamp - bidResponse.requestTimestamp; } /** diff --git a/src/auctionIndex.js b/src/auctionIndex.js index 0bf0fb88943..320e247de87 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -11,6 +11,7 @@ * Bid responses are not guaranteed to have a corresponding request. * @property {function({ requestId?: string }): *} getBidRequest Returns bidRequest object for requestId. * Bid responses are not guaranteed to have a corresponding request. + * @property {function} getOrtb2 Returns ortb2 object for bid */ /** diff --git a/src/bidfactory.ts b/src/bidfactory.ts index b87d3b14a26..8ce6bda3f23 100644 --- a/src/bidfactory.ts +++ b/src/bidfactory.ts @@ -135,7 +135,6 @@ export interface BaseBid extends ContextIdentifiers, Required { + const boolKeys = ['secondaryBidders', 'suppressStaleRender', 'suppressExpiredRender', 'legacyRender']; + const arrKeys = ['secondaryBidders'] + const allKeys = [].concat(boolKeys).concat(arrKeys); + + return function validateauctionOptions(val) { + if (!isPlainObject(val)) { + logWarn('Auction Options must be an object') + return false + } + + for (const k of Object.keys(val)) { + if (!allKeys.includes(k)) { + logWarn(`Auction Options given an incorrect param: ${k}`) + return false + } + if (arrKeys.includes(k)) { + if (!isArray(val[k])) { + logWarn(`Auction Options ${k} must be of type Array`); + return false + } else if (!val[k].every(isStr)) { + logWarn(`Auction Options ${k} must be only string`); + return false + } + } else if (boolKeys.includes(k)) { + if (!isBoolean(val[k])) { + logWarn(`Auction Options ${k} must be of type boolean`); + return false; + } + } + } + return true; + } + })(); function getProp(name) { return values[name]; } @@ -164,35 +198,6 @@ function attachProperties(config, useDefaultValues = true) { } return true; } - - function validateauctionOptions(val) { - if (!isPlainObject(val)) { - logWarn('Auction Options must be an object') - return false - } - - for (const k of Object.keys(val)) { - if (k !== 'secondaryBidders' && k !== 'suppressStaleRender' && k !== 'suppressExpiredRender') { - logWarn(`Auction Options given an incorrect param: ${k}`) - return false - } - if (k === 'secondaryBidders') { - if (!isArray(val[k])) { - logWarn(`Auction Options ${k} must be of type Array`); - return false - } else if (!val[k].every(isStr)) { - logWarn(`Auction Options ${k} must be only string`); - return false - } - } else if (k === 'suppressStaleRender' || k === 'suppressExpiredRender') { - if (!isBoolean(val[k])) { - logWarn(`Auction Options ${k} must be of type boolean`); - return false; - } - } - } - return true; - } } export interface Config { @@ -249,6 +254,11 @@ export interface Config { * https://docs.prebid.org/features/firstPartyData.html */ ortb2?: DeepPartial; + /** + * List of fingerprinting APIs to disable. When an API is listed, the corresponding library + * returns a safe default instead of reading the real value. Supported: 'devicepixelratio', 'webdriver', 'resolvedoptions'. + */ + disableFingerprintingApis?: Array<'devicepixelratio' | 'webdriver' | 'resolvedoptions'>; } type PartialConfig = Partial & { [setting: string]: unknown }; diff --git a/src/creativeRenderers.js b/src/creativeRenderers.js index 1297b2da4b6..dcbfd2b40ba 100644 --- a/src/creativeRenderers.js +++ b/src/creativeRenderers.js @@ -17,9 +17,22 @@ export const getCreativeRenderer = (function() { const src = getCreativeRendererSource(bidResponse); if (!renderers.hasOwnProperty(src)) { renderers[src] = new PbPromise((resolve) => { - const iframe = createInvisibleIframe(); - iframe.srcdoc = ``; - iframe.onload = () => resolve(iframe.contentWindow.render); + const iframe = createInvisibleIframe() + iframe.srcdoc = ` + + `; + const listenerForRendererReady = (event) => { + if (event.source !== iframe.contentWindow) return; + if (event.data?.type === `RENDERER_READY_${bidResponse.adId}`) { + window.removeEventListener('message', listenerForRendererReady); + resolve(iframe.contentWindow.render); + } + } + window.addEventListener('message', listenerForRendererReady); document.body.appendChild(iframe); }) } diff --git a/src/fpd/enrichment.ts b/src/fpd/enrichment.ts index b8102caa6ac..1ccaaaf6692 100644 --- a/src/fpd/enrichment.ts +++ b/src/fpd/enrichment.ts @@ -1,7 +1,17 @@ import {hook} from '../hook.js'; import {getRefererInfo, parseDomain} from '../refererDetection.js'; import {findRootDomain} from './rootDomain.js'; -import {deepSetValue, deepAccess, getDefinedParams, getWinDimensions, getDocument, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; +import { + deepSetValue, + deepAccess, + getDefinedParams, + getWinDimensions, + getDocument, + getWindowSelf, + getWindowTop, + mergeDeep, + memoize +} from '../utils.js'; import { getDNT } from '../../libraries/dnt/index.js'; import {config} from '../config.js'; import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; @@ -31,6 +41,19 @@ export interface FirstPartyDataConfig { * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData#returning_high_entropy_values */ uaHints?: string[] + /** + * Control keyword enrichment - `site.keywords`, `dooh.keywords` and/or `app.keywords`. + */ + keywords?: { + /** + * If true (the default), look for keywords in a keyword meta tag () and add them to first party data + */ + meta?: boolean, + /** + * If true (the default), look for keywords in a JSON-LD tag ("'; describe('greenbidsBidAdapter', () => { + const bidderRequestDefault = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + const bidRequests = [ + { + 'bidder': 'greenbids', + 'params': { + 'placementId': 4242 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + } + ]; + + function checkMediaTypesSizes(mediaTypes, expectedSizes) { + const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); + const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderRequestDefault); + const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); + + return payloadWithBannerSizes.data.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); + } + const adapter = newBidder(spec); let sandbox; @@ -62,7 +98,7 @@ describe('greenbidsBidAdapter', () => { it('should send bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); - expect(request.url).to.equal(ENDPOINT); + expect(request.url).to.equal(ENDPOINT_URL); expect(request.method).to.equal('POST'); }); @@ -242,7 +278,7 @@ describe('greenbidsBidAdapter', () => { it('should add pixelRatio info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); - const pixelRatio = window.top.devicePixelRatio + const pixelRatio = getDevicePixelRatio() expect(payload.devicePixelRatio).to.exist; expect(payload.devicePixelRatio).to.deep.equal(pixelRatio); @@ -319,74 +355,6 @@ describe('greenbidsBidAdapter', () => { expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); }); - - it('should add hardwareConcurrency info to payload', function () { - const originalHardwareConcurrency = window.top.navigator.hardwareConcurrency; - - const mockHardwareConcurrency = (value) => { - Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { - value, - configurable: true, - }); - }; - - try { - const mockValue = 8; - mockHardwareConcurrency(mockValue); - - const requestWithHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithHardwareConcurrency = JSON.parse(requestWithHardwareConcurrency.data); - - expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.exist; - expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.deep.equal(mockValue); - - mockHardwareConcurrency(undefined); - - const requestWithoutHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithoutHardwareConcurrency = JSON.parse(requestWithoutHardwareConcurrency.data); - - expect(payloadWithoutHardwareConcurrency.hardwareConcurrency).to.not.exist; - } finally { - Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { - value: originalHardwareConcurrency, - configurable: true, - }); - } - }); - - it('should add deviceMemory info to payload', function () { - const originalDeviceMemory = window.top.navigator.deviceMemory; - - const mockDeviceMemory = (value) => { - Object.defineProperty(window.top.navigator, 'deviceMemory', { - value, - configurable: true, - }); - }; - - try { - const mockValue = 4; - mockDeviceMemory(mockValue); - - const requestWithDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithDeviceMemory = JSON.parse(requestWithDeviceMemory.data); - - expect(payloadWithDeviceMemory.deviceMemory).to.exist; - expect(payloadWithDeviceMemory.deviceMemory).to.deep.equal(mockValue); - - mockDeviceMemory(undefined); - - const requestWithoutDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithoutDeviceMemory = JSON.parse(requestWithoutDeviceMemory.data); - - expect(payloadWithoutDeviceMemory.deviceMemory).to.not.exist; - } finally { - Object.defineProperty(window.top.navigator, 'deviceMemory', { - value: originalDeviceMemory, - configurable: true, - }); - } - }); }); describe('pageTitle', function () { @@ -690,14 +658,20 @@ describe('greenbidsBidAdapter', () => { it('should add schain info to payload if available', function () { const bidRequest = Object.assign({}, bidRequests[0], { - schain: { - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'example.com', - sid: '00001', - hp: 1 - }] + ortb2: { + source: { + ext: { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + } + } + } } }); @@ -904,6 +878,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'currency': 'USD', 'height': 250, + 'size': '200x100', 'bidId': '3ede2a3fa0db94', 'ttl': 360, 'width': 300, @@ -914,6 +889,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'currency': 'USD', 'height': 200, + 'size': '300x150', 'bidId': '4fef3b4gb1ec15', 'ttl': 360, 'width': 350, @@ -939,6 +915,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'width': 300, 'height': 250, + 'size': '200x100', 'currency': 'USD', 'netRevenue': true, 'meta': { @@ -953,6 +930,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'width': 350, 'height': 200, + 'size': '300x150', 'currency': 'USD', 'netRevenue': true, 'meta': { @@ -993,39 +971,3 @@ describe('greenbidsBidAdapter', () => { }); }); }); - -const bidderRequestDefault = { - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000 -}; - -const bidRequests = [ - { - 'bidder': 'greenbids', - 'params': { - 'placementId': 4242 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee', - 'deviceWidth': 1680 - } -]; - -function checkMediaTypesSizes(mediaTypes, expectedSizes) { - const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); - const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderRequestDefault); - const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); - - return payloadWithBannerSizes.data.forEach(bid => { - if (Array.isArray(expectedSizes)) { - expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); - } else { - expect(bid.sizes[0]).to.equal(expectedSizes); - } - }); -} diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 1ceaf4f2646..67caf24dba3 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -100,6 +100,39 @@ describe('gumgumAdapter', function () { describe('buildRequests', function () { const sizesArray = [[300, 250], [300, 600]]; + const id5Eid = { + source: 'id5-sync.com', + uids: [{ + id: 'uid-string', + ext: { + linkType: 2 + } + }] + }; + const pubProvidedIdEids = [ + { + uids: [ + { + ext: { + stype: 'ppuid', + }, + id: 'aac4504f-ef89-401b-a891-ada59db44336', + }, + ], + source: 'audigent.com', + }, + { + uids: [ + { + ext: { + stype: 'ppuid', + }, + id: 'y-zqTHmW9E2uG3jEETC6i6BjGcMhPXld2F~A', + }, + ], + source: 'crwdcntrl.net', + }, + ]; const bidderRequest = { ortb2: { site: { @@ -136,38 +169,7 @@ describe('gumgumAdapter', function () { sizes: sizesArray } }, - userId: { - id5id: { - uid: 'uid-string', - ext: { - linkType: 2 - } - } - }, - pubProvidedId: [ - { - uids: [ - { - ext: { - stype: 'ppuid', - }, - id: 'aac4504f-ef89-401b-a891-ada59db44336', - }, - ], - source: 'sonobi.com', - }, - { - uids: [ - { - ext: { - stype: 'ppuid', - }, - id: 'y-zqTHmW9E2uG3jEETC6i6BjGcMhPXld2F~A', - }, - ], - source: 'aol.com', - }, - ], + userIdAsEids: [id5Eid, ...pubProvidedIdEids], adUnitCode: 'adunit-code', sizes: sizesArray, bidId: '30b31c1838de1e', @@ -227,13 +229,139 @@ describe('gumgumAdapter', function () { it('should set pubProvidedId if the uid and pubProvidedId are available', function () { const request = { ...bidRequests[0] }; const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(bidRequests[0].userId.pubProvidedId)); + expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(pubProvidedIdEids)); + }); + it('should filter pubProvidedId entries by allowed sources', function () { + const filteredRequest = { + ...bidRequests[0], + userIdAsEids: [ + { + source: 'audigent.com', + uids: [{ id: 'ppid-1', ext: { stype: 'ppuid' } }] + }, + { + source: 'sonobi.com', + uids: [{ id: 'ppid-2', ext: { stype: 'ppuid' } }] + } + ] + }; + const bidRequest = spec.buildRequests([filteredRequest])[0]; + const pubProvidedIds = JSON.parse(bidRequest.data.pubProvidedId); + expect(pubProvidedIds.length).to.equal(1); + expect(pubProvidedIds[0].source).to.equal('audigent.com'); + }); + it('should not set pubProvidedId when all sources are filtered out', function () { + const filteredRequest = { + ...bidRequests[0], + userIdAsEids: [{ + source: 'sonobi.com', + uids: [{ id: 'ppid-2', ext: { stype: 'ppuid' } }] + }] + }; + const bidRequest = spec.buildRequests([filteredRequest])[0]; + expect(bidRequest.data.pubProvidedId).to.equal(undefined); }); it('should set id5Id and id5IdLinkType if the uid and linkType are available', function () { const request = { ...bidRequests[0] }; const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.id5Id).to.equal(bidRequests[0].userId.id5id.uid); - expect(bidRequest.data.id5IdLinkType).to.equal(bidRequests[0].userId.id5id.ext.linkType); + expect(bidRequest.data.id5Id).to.equal(id5Eid.uids[0].id); + expect(bidRequest.data.id5IdLinkType).to.equal(id5Eid.uids[0].ext.linkType); + }); + it('should use bidderRequest.ortb2.user.ext.eids when bid-level eids are not available', function () { + const request = { ...bidRequests[0], userIdAsEids: undefined }; + const fakeBidderRequest = { + ...bidderRequest, + ortb2: { + ...bidderRequest.ortb2, + user: { + ext: { + eids: [{ + source: 'liveramp.com', + uids: [{ + id: 'fallback-idl-env' + }] + }] + } + } + } + }; + const bidRequest = spec.buildRequests([request], fakeBidderRequest)[0]; + expect(bidRequest.data.idl_env).to.equal('fallback-idl-env'); + }); + it('should prioritize bidderRequest.ortb2.user.ext.eids over bid-level eids', function () { + const request = { + ...bidRequests[0], + userIdAsEids: [{ + source: 'liveramp.com', + uids: [{ id: 'bid-level-idl-env' }] + }] + }; + const fakeBidderRequest = { + ...bidderRequest, + ortb2: { + ...bidderRequest.ortb2, + user: { + ext: { + eids: [{ + source: 'liveramp.com', + uids: [{ id: 'ortb2-level-idl-env' }] + }] + } + } + } + }; + const bidRequest = spec.buildRequests([request], fakeBidderRequest)[0]; + expect(bidRequest.data.idl_env).to.equal('ortb2-level-idl-env'); + }); + it('should keep identity output consistent for prebid10 ortb2 eids input', function () { + const request = { ...bidRequests[0], userIdAsEids: undefined }; + const fakeBidderRequest = { + ...bidderRequest, + ortb2: { + ...bidderRequest.ortb2, + user: { + ext: { + eids: [ + { + source: 'uidapi.com', + uids: [{ id: 'uid2-token', atype: 3 }] + }, + { + source: 'liveramp.com', + uids: [{ id: 'idl-envelope', atype: 1 }] + }, + { + source: 'adserver.org', + uids: [{ id: 'tdid-value', atype: 1, ext: { rtiPartner: 'TDID' } }] + }, + { + source: 'id5-sync.com', + uids: [{ id: 'id5-value', atype: 1, ext: { linkType: 2 } }] + }, + { + source: 'audigent.com', + uids: [{ id: 'ppid-1', atype: 1, ext: { stype: 'ppuid' } }] + }, + { + source: 'sonobi.com', + uids: [{ id: 'ppid-2', atype: 1, ext: { stype: 'ppuid' } }] + } + ] + } + } + } + }; + const bidRequest = spec.buildRequests([request], fakeBidderRequest)[0]; + + // Expected identity payload shape from legacy GumGum request fields. + expect(bidRequest.data.uid2).to.equal('uid2-token'); + expect(bidRequest.data.idl_env).to.equal('idl-envelope'); + expect(bidRequest.data.tdid).to.equal('tdid-value'); + expect(bidRequest.data.id5Id).to.equal('id5-value'); + expect(bidRequest.data.id5IdLinkType).to.equal(2); + const pubProvidedId = JSON.parse(bidRequest.data.pubProvidedId); + expect(pubProvidedId.length).to.equal(1); + expect(pubProvidedId[0].source).to.equal('audigent.com'); }); it('should set pubId param if found', function () { @@ -614,7 +742,7 @@ describe('gumgumAdapter', function () { it('should set pubProvidedId if the uid and pubProvidedId are available', function () { const request = { ...bidRequests[0] }; const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(bidRequests[0].userId.pubProvidedId)); + expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(pubProvidedIdEids)); }); it('should add gdpr consent parameters if gdprConsent is present', function () { @@ -714,14 +842,40 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.uspConsent).to.eq(uspConsentObj.uspConsent); }); it('should add a tdid parameter if request contains unified id from TradeDesk', function () { - const unifiedId = { - 'userId': { - 'tdid': 'tradedesk-id' - } - } - const request = Object.assign(unifiedId, bidRequests[0]); + const tdidEid = { + source: 'adserver.org', + uids: [{ + id: 'tradedesk-id', + ext: { + rtiPartner: 'TDID' + } + }] + }; + const request = Object.assign({}, bidRequests[0], { userIdAsEids: [...bidRequests[0].userIdAsEids, tdidEid] }); + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.tdid).to.eq(tdidEid.uids[0].id); + }); + it('should add a tdid parameter when TDID uid is not the first uid in adserver.org', function () { + const tdidEid = { + source: 'adserver.org', + uids: [ + { + id: 'non-tdid-first', + ext: { + rtiPartner: 'NOT_TDID' + } + }, + { + id: 'tradedesk-id', + ext: { + rtiPartner: 'TDID' + } + } + ] + }; + const request = Object.assign({}, bidRequests[0], { userIdAsEids: [tdidEid] }); const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.tdid).to.eq(unifiedId.userId.tdid); + expect(bidRequest.data.tdid).to.eq('tradedesk-id'); }); it('should not add a tdid parameter if unified id is not found', function () { const request = spec.buildRequests(bidRequests)[0]; @@ -729,7 +883,8 @@ describe('gumgumAdapter', function () { }); it('should send IDL envelope ID if available', function () { const idl_env = 'abc123'; - const request = { ...bidRequests[0], userId: { idl_env } }; + const idlEid = { source: 'liveramp.com', uids: [{ id: idl_env }] }; + const request = { ...bidRequests[0], userIdAsEids: [idlEid] }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('idl_env'); @@ -743,7 +898,8 @@ describe('gumgumAdapter', function () { }); it('should add a uid2 parameter if request contains uid2 id', function () { const uid2 = { id: 'sample-uid2' }; - const request = { ...bidRequests[0], userId: { uid2 } }; + const uid2Eid = { source: 'uidapi.com', uids: [{ id: uid2.id }] }; + const request = { ...bidRequests[0], userIdAsEids: [uid2Eid] }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('uid2'); diff --git a/test/spec/modules/harionBidAdapter_spec.js b/test/spec/modules/harionBidAdapter_spec.js new file mode 100644 index 00000000000..d5a1dd509a6 --- /dev/null +++ b/test/spec/modules/harionBidAdapter_spec.js @@ -0,0 +1,475 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/harionBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'Harion'; + +describe('HarionBidAdapter', 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 () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + 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 + } + ]; + + const 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); + const 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); + const 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] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const 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]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + 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 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const 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 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const 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 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const 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' + }] + }; + + const 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' + }] + }; + const 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', + }] + }; + const 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' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/humansecurityRtdProvider_spec.js b/test/spec/modules/humansecurityRtdProvider_spec.js index c6ad163c544..2104ffc6edd 100644 --- a/test/spec/modules/humansecurityRtdProvider_spec.js +++ b/test/spec/modules/humansecurityRtdProvider_spec.js @@ -10,10 +10,7 @@ const { SUBMODULE_NAME, SCRIPT_URL, main, - load, - onImplLoaded, - onImplMessage, - onGetBidRequestData + load } = __TEST__; describe('humansecurity RTD module', function () { @@ -23,20 +20,20 @@ describe('humansecurity RTD module', function () { const sonarStubId = `sonar_${stubUuid}`; const stubWindow = { [sonarStubId]: undefined }; - beforeEach(function() { + beforeEach(function () { sandbox = sinon.createSandbox(); sandbox.stub(utils, 'getWindowSelf').returns(stubWindow); sandbox.stub(utils, 'generateUUID').returns(stubUuid); sandbox.stub(refererDetection, 'getRefererInfo').returns({ domain: 'example.com' }); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); describe('Initialization step', function () { let sandbox2; let connectSpy; - beforeEach(function() { + beforeEach(function () { sandbox2 = sinon.createSandbox(); connectSpy = sandbox.spy(); // Once the impl script is loaded, it registers the API using session ID @@ -46,6 +43,17 @@ describe('humansecurity RTD module', function () { sandbox2.restore(); }); + it('should connect to the implementation script once it loads', function () { + load({}); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + const callback = loadExternalScriptStub.getCall(0).args[3]; + expect(callback).to.be.a('function'); + const args = connectSpy.getCall(0).args; + expect(args[0]).to.haveOwnProperty('cmd'); // pbjs global + expect(args[0]).to.haveOwnProperty('que'); + }); + it('should accept valid configurations', function () { // Default configuration - empty expect(() => load({})).to.not.throw(); @@ -62,14 +70,19 @@ describe('humansecurity RTD module', function () { }); it('should insert implementation script', () => { - load({ }); + 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[0]).to.include(`${SCRIPT_URL}?r=example.com`); + const mvMatch = args[0].match(/[?&]mv=([^&]+)/); + expect(mvMatch).to.not.equal(null); + const mvValue = Number(mvMatch[1]); + expect(Number.isFinite(mvValue)).to.equal(true); + expect(mvValue).to.be.greaterThan(0); expect(args[2]).to.be.equal(SUBMODULE_NAME); - expect(args[3]).to.be.equal(onImplLoaded); + expect(args[3]).to.be.a('function'); expect(args[4]).to.be.equal(null); expect(args[5]).to.be.deep.equal({ 'data-sid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }); }); @@ -80,90 +93,14 @@ describe('humansecurity RTD module', function () { 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.createSandbox(); - 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); + expect(args[0]).to.include(`${SCRIPT_URL}?r=example.com&c=customer123`); }); }); - describe('Sumbodule execution', function() { + describe('Submodule execution', function () { let sandbox2; let submoduleStub; - beforeEach(function() { + beforeEach(function () { sandbox2 = sinon.createSandbox(); submoduleStub = sandbox2.stub(hook, 'submodule'); }); @@ -203,7 +140,7 @@ describe('humansecurity RTD module', function () { it('should commence initialization on default initialization', function () { const { init } = getModule(); - expect(init({ })).to.equal(true); + expect(init({})).to.equal(true); expect(loadExternalScriptStub.calledOnce).to.be.true; }); }); diff --git a/test/spec/modules/hypelabBidAdapter_spec.js b/test/spec/modules/hypelabBidAdapter_spec.js index d6c79a957cc..ff98c33b136 100644 --- a/test/spec/modules/hypelabBidAdapter_spec.js +++ b/test/spec/modules/hypelabBidAdapter_spec.js @@ -13,6 +13,7 @@ import { } from 'modules/hypelabBidAdapter.js'; import { BANNER } from 'src/mediaTypes.js'; +import {getDevicePixelRatio} from '../../../libraries/devicePixelRatio/devicePixelRatio.js'; const mockValidBidRequest = { bidder: 'hypelab', @@ -177,7 +178,7 @@ describe('hypelabBidAdapter', function () { expect(data.dpr).to.be.a('number'); expect(data.location).to.be.a('string'); expect(data.floor).to.equal(null); - expect(data.dpr).to.equal(1); + expect(data.dpr).to.equal(getDevicePixelRatio()); expect(data.wp).to.deep.equal({ ada: false, bnb: false, diff --git a/test/spec/modules/id5AnalyticsAdapter_spec.js b/test/spec/modules/id5AnalyticsAdapter_spec.js index e213494ce78..1e1df900f81 100644 --- a/test/spec/modules/id5AnalyticsAdapter_spec.js +++ b/test/spec/modules/id5AnalyticsAdapter_spec.js @@ -1,12 +1,13 @@ import adapterManager from '../../../src/adapterManager.js'; import id5AnalyticsAdapter from '../../../modules/id5AnalyticsAdapter.js'; -import { expect } from 'chai'; +import {expect} from 'chai'; import * as events from '../../../src/events.js'; -import { EVENTS } from '../../../src/constants.js'; -import { generateUUID } from '../../../src/utils.js'; +import {EVENTS} from '../../../src/constants.js'; +import {generateUUID} from '../../../src/utils.js'; import {server} from '../../mocks/xhr.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {enrichEidsRule} from "../../../modules/tcfControl.ts"; +import * as utils from '../../../src/utils.js'; const CONFIG_URL = 'https://api.id5-sync.com/analytics/12349/pbjs'; const INGEST_URL = 'https://test.me/ingest'; @@ -20,6 +21,7 @@ describe('ID5 analytics adapter', () => { config = { options: { partnerId: 12349, + compressionDisabled: true } }; }); @@ -129,6 +131,32 @@ describe('ID5 analytics adapter', () => { expect(body2.payload).to.eql(auction); }); + it('compresses large events with gzip when enabled', async function() { + // turn ON compression + config.options.compressionDisabled = false; + + const longCode = 'x'.repeat(2048); + auction.adUnits[0].code = longCode; + auction.adUnits[0].adUnitCodes = [longCode]; + + id5AnalyticsAdapter.enableAnalytics(config); + server.respond(); + events.emit(EVENTS.AUCTION_END, auction); + server.respond(); + + // Wait as gzip stream is async, we need to wait until it is processed. 3 requests: config, tcf2Enforcement, auctionEnd + await waitForRequests(3); + const eventReq = server.requests[2]; + if (utils.isGzipCompressionSupported()) { + expect(eventReq.requestHeaders['Content-Encoding']).to.equal('gzip'); + expect(eventReq.requestBody).to.be.instanceof(Uint8Array); + } else { // compression is not supported in some test browsers, so we expect the event to be uncompressed. + expect(eventReq.requestHeaders['Content-Encoding']).to.be.undefined; + const body = JSON.parse(eventReq.requestBody); + expect(body.event).to.equal(EVENTS.AUCTION_END); + } + }); + it('does not repeat already sent events on new events', () => { id5AnalyticsAdapter.enableAnalytics(config); server.respond(); @@ -160,7 +188,7 @@ describe('ID5 analytics adapter', () => { 'criteoId': '_h_y_19IMUhMZG1TOTRReHFNc29TekJ3TzQ3elhnRU81ayUyQjhiRkdJJTJGaTFXJTJCdDRnVmN4S0FETUhQbXdmQWg0M3g1NWtGbGolMkZXalclMkJvWjJDOXFDSk1HU3ZKaVElM0QlM0Q', 'id5id': { 'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu', - 'ext': { 'linkType': 1 } + 'ext': {'linkType': 1} }, 'tdid': '888a6042-8f99-483b-aa26-23c44bc9166b' }, @@ -175,7 +203,7 @@ describe('ID5 analytics adapter', () => { 'uids': [{ 'id': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu', 'atype': 1, - 'ext': { 'linkType': 1 } + 'ext': {'linkType': 1} }] }] }]; @@ -489,7 +517,7 @@ describe('ID5 analytics adapter', () => { 'userId': { 'id5id': { 'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu', - 'ext': { 'linkType': 1 } + 'ext': {'linkType': 1} } } }]; @@ -511,5 +539,21 @@ describe('ID5 analytics adapter', () => { expect(body.payload.bidsReceived[0].meta).to.equal(undefined); // new rule expect(body.payload.adUnits[0].bids[0].userId.id5id.uid).to.equal(auction.adUnits[0].bids[0].userId.id5id.uid); // old, overridden rule }); + + // helper to wait until server has received at least `expected` requests + async function waitForRequests(expected = 3, timeout = 2000, interval = 10) { + return new Promise((resolve, reject) => { + const start = Date.now(); + const timer = setInterval(() => { + if (server.requests.length >= expected) { + clearInterval(timer); + resolve(); + } else if (Date.now() - start > timeout) { + clearInterval(timer); + reject(new Error('Timed out waiting for requests: expected ' + expected)); + } + }, interval); + }); + } }); }); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 7249560c8c9..5964b683372 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1361,13 +1361,8 @@ describe('ID5 ID System', function () { setTargetingStub = sinon.stub(); window.googletag = { cmd: [], - pubads: function () { - return { - setTargeting: setTargetingStub - }; - } + setConfig: setTargetingStub }; - sinon.spy(window.googletag, 'pubads'); storedObject = utils.deepClone(ID5_STORED_OBJ); }); @@ -1391,14 +1386,16 @@ describe('ID5 ID System', function () { for (const [tagName, tagValue] of Object.entries(tagsObj)) { const fullTagName = `${targetingEnabledConfig.params.gamTargetingPrefix}_${tagName}`; - const matchingCall = setTargetingStub.getCalls().find(call => call.args[0] === fullTagName); + const matchingCall = setTargetingStub.getCalls().find(call => { + const config = call.args[0]; + return config.targeting && config.targeting[fullTagName] !== undefined; + }); expect(matchingCall, `Tag ${fullTagName} was not set`).to.exist; - expect(matchingCall.args[1]).to.equal(tagValue); + expect(matchingCall.args[0].targeting[fullTagName]).to.equal(tagValue); } window.googletag.cmd = []; setTargetingStub.reset(); - window.googletag.pubads.resetHistory(); } it('should not set GAM targeting if it is not enabled', function () { @@ -1435,6 +1432,306 @@ describe('ID5 ID System', function () { }) }) + describe('Decode should also expose targeting via id5tags if configured', function () { + let origId5tags, storedObject; + const exposeTargetingConfig = getId5FetchConfig(); + exposeTargetingConfig.params.gamTargetingPrefix = 'id5'; + exposeTargetingConfig.params.exposeTargeting = true; + + beforeEach(function () { + delete window.id5tags; + storedObject = utils.deepClone(ID5_STORED_OBJ); + }); + + afterEach(function () { + delete window.id5tags; + id5System.id5IdSubmodule._reset(); + }); + + it('should not expose targeting if exposeTargeting is not enabled', function () { + const config = getId5FetchConfig(); + config.params.gamTargetingPrefix = 'id5'; + // exposeTargeting is not set + const testObj = { + ...storedObject, + 'tags': { + 'id': 'y', + 'ab': 'n' + } + }; + id5System.id5IdSubmodule.decode(testObj, config); + expect(window.id5tags).to.be.undefined; + }); + + it('should not expose targeting if tags not returned from server', function () { + // tags is not in the response + id5System.id5IdSubmodule.decode(storedObject, exposeTargetingConfig); + expect(window.id5tags).to.be.undefined; + }); + + it('should create id5tags.cmd when it does not exist pre-decode', function () { + const testObj = { + ...storedObject, + 'tags': { + 'id': 'y', + 'ab': 'n' + } + }; + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + expect(window.id5tags).to.exist; + expect(window.id5tags.cmd).to.be.an('array'); + expect(window.id5tags.tags).to.deep.equal({ + 'id': 'y', + 'ab': 'n' + }); + }); + + it('should execute queued functions when cmd was created earlier', async function () { + const testTags = { + 'id': 'y', + 'ab': 'n', + 'enrich': 'y' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + const callTracker = []; + let resolvePromise; + const callbackPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + // Pre-create id5tags with queued functions + window.id5tags = { + cmd: [ + (tags) => callTracker.push({call: 1, tags: tags}), + (tags) => callTracker.push({call: 2, tags: tags}), + (tags) => { + callTracker.push({call: 3, tags: tags}); + resolvePromise(); + } + ] + }; + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + await callbackPromise; + + // Verify all queued functions were called with the tags + expect(callTracker).to.have.lengthOf(3); + expect(callTracker[0]).to.deep.equal({call: 1, tags: testTags}); + expect(callTracker[1]).to.deep.equal({call: 2, tags: testTags}); + expect(callTracker[2]).to.deep.equal({call: 3, tags: testTags}); + + // Verify tags were stored + expect(window.id5tags.tags).to.deep.equal(testTags); + }); + + it('should override push method to execute functions immediately', function () { + const testTags = { + 'id': 'y', + 'ab': 'n' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + // Now push a new function and verify it executes immediately + let callResult = null; + window.id5tags.cmd.push((tags) => { + callResult = {executed: true, tags: tags}; + }); + + expect(callResult).to.not.be.null; + expect(callResult.executed).to.be.true; + expect(callResult.tags).to.deep.equal(testTags); + }); + + it('should retrigger functions when tags are different but not when tags are the same', async function () { + const firstTags = { + 'id': 'y', + 'ab': 'n' + }; + const secondTags = { + 'id': 'y', + 'ab': 'y', + 'enrich': 'y' + }; + + const firstObj = { + ...storedObject, + 'tags': firstTags + }; + + const callTracker = []; + + // First decode + let resolveFirstPromise; + const firstCallbackPromise = new Promise((resolve) => { + resolveFirstPromise = resolve; + }); + + window.id5tags = { + cmd: [ + (tags) => { + callTracker.push({call: 'decode', tags: utils.deepClone(tags)}); + resolveFirstPromise(); + } + ] + }; + + id5System.id5IdSubmodule.decode(firstObj, exposeTargetingConfig); + + await firstCallbackPromise; + + expect(callTracker).to.have.lengthOf(1); + expect(callTracker[0].tags).to.deep.equal(firstTags); + + // Second decode with different tags - should retrigger + const secondObj = { + ...storedObject, + 'tags': secondTags + }; + + let resolveSecondPromise; + const secondCallbackPromise = new Promise((resolve) => { + resolveSecondPromise = resolve; + }); + + // Update the callback to resolve when called again + window.id5tags.cmd[0] = (tags) => { + callTracker.push({call: 'decode', tags: utils.deepClone(tags)}); + resolveSecondPromise(); + }; + + id5System.id5IdSubmodule.decode(secondObj, exposeTargetingConfig); + + await secondCallbackPromise; + + // The queued function should be called again with new tags + expect(callTracker).to.have.lengthOf(2); + expect(callTracker[1].tags).to.deep.equal(secondTags); + expect(window.id5tags.tags).to.deep.equal(secondTags); + + // Third decode with identical tags content (but different object reference) - should NOT retrigger + const thirdObj = { + ...storedObject, + 'tags': { + 'id': 'y', + 'ab': 'y', + 'enrich': 'y' + } + }; + + id5System.id5IdSubmodule.decode(thirdObj, exposeTargetingConfig); + + // Give it a small delay to ensure it doesn't retrigger + await new Promise(resolve => setTimeout(resolve, 50)); + + // With deepEqual, this should NOT retrigger since content is the same as secondTags + expect(callTracker).to.have.lengthOf(2); + expect(window.id5tags.tags).to.deep.equal(secondTags); + }); + + it('should handle when someone else has set id5tags.cmd earlier', async function () { + const testTags = { + 'id': 'y', + 'ab': 'n' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + const externalCallTracker = []; + let resolvePromise; + const callbackPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + // External script creates id5tags + window.id5tags = { + cmd: [], + externalData: 'some-external-value' + }; + + // Add external function + window.id5tags.cmd.push((tags) => { + externalCallTracker.push({external: true, tags: tags}); + resolvePromise(); + }); + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + await callbackPromise; + + // External function should be called + expect(externalCallTracker).to.have.lengthOf(1); + expect(externalCallTracker[0].external).to.be.true; + expect(externalCallTracker[0].tags).to.deep.equal(testTags); + + // External data should be preserved + expect(window.id5tags.externalData).to.equal('some-external-value'); + + // Tags should be set + expect(window.id5tags.tags).to.deep.equal(testTags); + }); + + it('should work with both gamTargetingPrefix and exposeTargeting enabled', async function () { + // Setup googletag + const origGoogletag = window.googletag; + window.googletag = { + cmd: [], + setConfig: sinon.stub() + }; + + const testTags = { + 'id': 'y', + 'ab': 'n' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + const callTracker = []; + let resolvePromise; + const callbackPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + window.id5tags = { + cmd: [(tags) => { + callTracker.push(tags); + resolvePromise(); + }] + }; + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + await callbackPromise; + + // Both mechanisms should work + expect(window.googletag.cmd.length).to.be.at.least(1); + expect(callTracker).to.have.lengthOf(1); + expect(callTracker[0]).to.deep.equal(testTags); + expect(window.id5tags.tags).to.deep.equal(testTags); + + // Restore + if (origGoogletag) { + window.googletag = origGoogletag; + } else { + delete window.googletag; + } + }); + }); + describe('A/B Testing', function () { const expectedDecodedObjectWithIdAbOff = {id5id: {uid: ID5_STORED_ID, ext: {linkType: ID5_STORED_LINK_TYPE}}}; const expectedDecodedObjectWithIdAbOn = { diff --git a/test/spec/modules/incrementxBidAdapter_spec.js b/test/spec/modules/incrementxBidAdapter_spec.js index 3fcf3bcc978..47008ac738e 100644 --- a/test/spec/modules/incrementxBidAdapter_spec.js +++ b/test/spec/modules/incrementxBidAdapter_spec.js @@ -1,51 +1,34 @@ import { expect } from 'chai'; import { spec } from 'modules/incrementxBidAdapter.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { INSTREAM, OUTSTREAM } from 'src/video.js'; -describe('incrementx', function () { +describe('incrementxBidAdapter', function () { const bannerBidRequest = { bidder: 'incrementx', - params: { - placementId: 'IX-HB-12345' - }, - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - sizes: [ - [300, 250], - [300, 600] - ], + params: { placementId: 'IX-HB-12345' }, + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + sizes: [[300, 250], [300, 600]], adUnitCode: 'div-gpt-ad-1460505748561-0', bidId: '2faedf3e89d123', bidderRequestId: '1c78fb49cc71c6', auctionId: 'b4f81e8e36232', transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fb' }; - const videoBidRequest = { + + const outstreamVideoBidRequest = { bidder: 'incrementx', - params: { - placementId: 'IX-HB-12346' - }, - mediaTypes: { - video: { - context: 'outstream', - playerSize: ['640x480'] - } - }, + params: { placementId: 'IX-HB-12346' }, + mediaTypes: { video: { context: 'outstream', playerSize: [640, 480] } }, adUnitCode: 'div-gpt-ad-1460505748561-1', bidId: '2faedf3e89d124', bidderRequestId: '1c78fb49cc71c7', auctionId: 'b4f81e8e36233', transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fc' }; + const instreamVideoBidRequest = { bidder: 'incrementx', - params: { - placementId: 'IX-HB-12347' - }, + params: { placementId: 'IX-HB-12347' }, mediaTypes: { video: { context: 'instream', @@ -64,166 +47,622 @@ describe('incrementx', function () { transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fd' }; - describe('isBidRequestValid', function () { - it('should return true when required params are found', function () { + const bidderRequest = { + refererInfo: { page: 'https://example.com' } + }; + + // VALIDATION + + describe('isBidRequestValid', () => { + it('should return true when placementId exists', () => { expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(true); - expect(spec.isBidRequestValid(videoBidRequest)).to.equal(true); + expect(spec.isBidRequestValid(outstreamVideoBidRequest)).to.equal(true); expect(spec.isBidRequestValid(instreamVideoBidRequest)).to.equal(true); }); + + it('should return false when placementId is missing', () => { + const invalidBid = { bidder: 'incrementx', params: {} }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when params is missing', () => { + const invalidBid = { bidder: 'incrementx' }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); }); - describe('buildRequests', function () { - const bidderRequest = { - refererInfo: { - page: 'https://someurl.com' - } - }; - 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 outstream 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); - // For video, bidderRequestData should be included - expect(requests[0].data.bidderRequestData).to.exist; - }); - it('should build instream video request', function () { - const requests = spec.buildRequests([instreamVideoBidRequest], 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-12347'); - expect(data.sizes).to.be.a('array'); - expect(data.mChannel).to.equal(2); - - // For video, bidderRequestData should be included - expect(requests[0].data.bidderRequestData).to.exist; - const decodedBidderRequestData = decodeURI(requests[0].data.bidderRequestData); - expect(decodedBidderRequestData).to.be.a('string'); - // Verify it can be parsed as JSON - expect(() => JSON.parse(decodedBidderRequestData)).to.not.throw(); + // BUILD REQUESTS TESTS (LEGACY FORMAT ONLY) + + describe('buildRequests', () => { + it('should build a valid banner request (LEGACY FORMAT: q only)', () => { + const reqs = spec.buildRequests([bannerBidRequest], bidderRequest); + const data = reqs[0].data; + + expect(reqs[0].method).to.equal('POST'); + expect(reqs[0].url).to.equal('https://hb.incrementxserv.com/vzhbidder/bid'); + + // Banner sends ONLY q + expect(data.q).to.exist; + expect(data.bidderRequestData).to.not.exist; + + const decodedQ = JSON.parse(decodeURIComponent(data.q)); + expect(decodedQ._vzPlacementId).to.equal('IX-HB-12345'); + expect(decodedQ.mChannel).to.equal(1); + expect(decodedQ._rqsrc).to.equal('https://example.com'); + expect(decodedQ._slotBidId).to.equal('2faedf3e89d123'); + expect(decodedQ.sizes).to.be.an('array'); + }); + + it('should build an outstream video request (LEGACY FORMAT: q + bidderRequestData)', () => { + const reqs = spec.buildRequests([outstreamVideoBidRequest], bidderRequest); + const data = reqs[0].data; + + // Video sends q + bidderRequestData ONLY + expect(data.q).to.exist; + expect(data.bidderRequestData).to.exist; + + const decodedQ = JSON.parse(decodeURIComponent(data.q)); + expect(decodedQ._vzPlacementId).to.equal('IX-HB-12346'); + expect(decodedQ.mChannel).to.equal(2); + + // bidderRequestData contains full bidderRequest + const decodedBidderRequest = JSON.parse(decodeURIComponent(data.bidderRequestData)); + expect(decodedBidderRequest.refererInfo).to.exist; + expect(decodedBidderRequest.refererInfo.page).to.equal('https://example.com'); + }); + + it('should build an instream video request (LEGACY FORMAT)', () => { + const reqs = spec.buildRequests([instreamVideoBidRequest], bidderRequest); + const data = reqs[0].data; + + expect(data.q).to.exist; + expect(data.bidderRequestData).to.exist; + + const decodedQ = JSON.parse(decodeURIComponent(data.q)); + expect(decodedQ.mChannel).to.equal(2); + expect(decodedQ._vzPlacementId).to.equal('IX-HB-12347'); + }); + + it('should handle multiple bid requests', () => { + const reqs = spec.buildRequests([bannerBidRequest, outstreamVideoBidRequest], bidderRequest); + expect(reqs).to.have.lengthOf(2); + expect(reqs[0].data.q).to.exist; + expect(reqs[1].data.q).to.exist; + }); + + it('should use params.size if available', () => { + const bidWithParamsSize = { + ...bannerBidRequest, + params: { placementId: 'IX-HB-12345', size: [[728, 90]] } + }; + const reqs = spec.buildRequests([bidWithParamsSize], bidderRequest); + const decodedQ = JSON.parse(decodeURIComponent(reqs[0].data.q)); + expect(decodedQ.sizes).to.be.an('array'); }); }); - describe('interpretResponse', function () { - const bannerServerResponse = { + // INTERPRET RESPONSE - BANNER + + describe('interpretResponse - banner', () => { + const bannerResponse = { body: { slotBidId: '2faedf3e89d123', - cpm: 0.5, + ad: '
BANNER
', + cpm: 1.5, + mediaType: BANNER, adWidth: 300, adHeight: 250, - ad: '
Banner Ad
', - mediaType: BANNER, - netRevenue: true, currency: 'USD', + netRevenue: true, + creativeId: 'CR123', + adType: '1', + settings: { test: 'value' }, advertiserDomains: ['example.com'] } }; - const videoServerResponse = { + + it('should parse banner response correctly', () => { + const req = { data: { q: 'dummy' } }; + const result = spec.interpretResponse(bannerResponse, req); + + expect(result).to.have.lengthOf(1); + const bid = result[0]; + expect(bid.requestId).to.equal('2faedf3e89d123'); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.ad).to.equal('
BANNER
'); + expect(bid.cpm).to.equal(1.5); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.creativeId).to.equal('CR123'); + expect(bid.ttl).to.equal(300); + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + }); + + it('should handle banner with missing ad content', () => { + const responseNoAd = { + body: { + slotBidId: '2faedf3e89d123', + cpm: 1.5, + mediaType: BANNER, + adWidth: 300, + adHeight: 250 + } + }; + const req = { data: { q: 'dummy' } }; + const result = spec.interpretResponse(responseNoAd, req); + expect(result[0].ad).to.equal(''); + }); + + it('should use default currency when not provided', () => { + const responseNoCurrency = { + body: { + slotBidId: '2faedf3e89d123', + ad: '
BANNER
', + cpm: 1.5, + mediaType: BANNER, + adWidth: 300, + adHeight: 250 + } + }; + const req = { data: { q: 'dummy' } }; + const result = spec.interpretResponse(responseNoCurrency, req); + expect(result[0].currency).to.equal('USD'); + }); + + it('should use default values for missing fields', () => { + const minimalResponse = { + body: { + slotBidId: '2faedf3e89d123', + cpm: 0, + mediaType: BANNER + } + }; + const req = { data: { q: 'dummy' } }; + const result = spec.interpretResponse(minimalResponse, req); + const bid = result[0]; + expect(bid.cpm).to.equal(0); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.adType).to.equal('1'); + expect(bid.creativeId).to.equal(0); + expect(bid.netRevenue).to.equal(false); + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); + }); + + // INTERPRET RESPONSE - VIDEO (with videoContext) + + describe('interpretResponse - video with videoContext', () => { + const outstreamResponse = { body: { slotBidId: '2faedf3e89d124', - cpm: 1.0, + ad: 'outstream', + cpm: 2, + mediaType: VIDEO, adWidth: 640, adHeight: 480, - ad: 'Test VAST', - mediaType: VIDEO, + rUrl: 'https://cdn/test.xml', netRevenue: true, currency: 'USD', - rUrl: 'https://example.com/vast.xml', advertiserDomains: ['example.com'] } }; - const instreamVideoServerResponse = { + + const instreamResponse = { body: { slotBidId: '2faedf3e89d125', - cpm: 1.5, + ad: 'instream', + cpm: 3, + mediaType: VIDEO, adWidth: 640, adHeight: 480, - ad: 'Test Instream VAST', - mediaType: VIDEO, + advertiserDomains: ['example.com'], netRevenue: true, currency: 'USD', ttl: 300, - advertiserDomains: ['example.com'] } }; - const bidderRequest = { - refererInfo: { - page: 'https://someurl.com' - }, - data: { - bidderRequestData: JSON.stringify({ - bids: [videoBidRequest] - }) + it('should parse outstream video using videoContext field', () => { + const req = { + data: { + videoContext: 'outstream', + adUnitCode: 'ad-unit-outstream' + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res).to.have.lengthOf(1); + expect(res[0].vastXml).to.equal('outstream'); + expect(res[0].renderer).to.exist; + expect(res[0].renderer.url).to.equal('https://cdn/test.xml'); + expect(res[0].renderer.id).to.equal('2faedf3e89d124'); + }); + + it('should parse instream video using videoContext field', () => { + const req = { + data: { + videoContext: 'instream', + adUnitCode: 'ad-unit-instream' + } + }; + + const res = spec.interpretResponse(instreamResponse, req); + expect(res).to.have.lengthOf(1); + expect(res[0].vastUrl).to.equal('instream'); + expect(res[0].renderer).to.not.exist; + }); + + it('should not create renderer for outstream without rUrl', () => { + const responseNoRUrl = { + body: { + slotBidId: '2faedf3e89d124', + ad: 'outstream', + cpm: 2, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480 + } + }; + const req = { + data: { + videoContext: 'outstream', + adUnitCode: 'ad-unit-outstream' + } + }; + + const res = spec.interpretResponse(responseNoRUrl, req); + expect(res[0].renderer).to.not.exist; + }); + }); + + // INTERPRET RESPONSE - VIDEO (legacy bidderRequestData) + + describe('interpretResponse - video with legacy bidderRequestData', () => { + const outstreamResponse = { + body: { + slotBidId: '2faedf3e89d124', + ad: 'outstream', + cpm: 2, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480, + rUrl: 'https://cdn/test.xml', + advertiserDomains: ['example.com'] } }; - const instreamBidderRequest = { - refererInfo: { - page: 'https://someurl.com' - }, - data: { - bidderRequestData: JSON.stringify({ - bids: [instreamVideoBidRequest] - }) + const instreamResponse = { + body: { + slotBidId: '2faedf3e89d125', + ad: 'instream', + cpm: 3, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480, + advertiserDomains: ['example.com'] } }; - 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 parse outstream video from bidderRequestData', () => { + const req = { + data: { + bidderRequestData: encodeURIComponent(JSON.stringify({ + bids: [{ + bidId: '2faedf3e89d124', + adUnitCode: 'ad-unit-outstream', + mediaTypes: { video: { context: 'outstream' } } + }] + })) + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res[0].vastXml).to.equal('outstream'); + expect(res[0].renderer).to.exist; }); - it('should handle outstream 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']); + it('should parse instream video from bidderRequestData', () => { + const req = { + data: { + bidderRequestData: encodeURIComponent(JSON.stringify({ + bids: [{ + bidId: '2faedf3e89d125', + adUnitCode: 'ad-unit-instream', + mediaTypes: { video: { context: 'instream' } } + }] + })) + } + }; + + const res = spec.interpretResponse(instreamResponse, req); + expect(res[0].vastUrl).to.equal('instream'); + expect(res[0].renderer).to.not.exist; }); - it('should handle instream video response', function () { - const bidResponses = spec.interpretResponse(instreamVideoServerResponse, instreamBidderRequest); - expect(bidResponses).to.have.lengthOf(1); - const bid = bidResponses[0]; - expect(bid.requestId).to.equal('2faedf3e89d125'); - expect(bid.cpm).to.equal(1.5); - expect(bid.width).to.equal(640); - expect(bid.height).to.equal(480); - expect(bid.vastUrl).to.equal('Test Instream VAST'); - expect(bid.mediaType).to.equal(VIDEO); - expect(bid.ttl).to.equal(300); - expect(bid.renderer).to.not.exist; - expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + it('should handle bidderRequestData as object (not string)', () => { + const req = { + data: { + bidderRequestData: { + bids: [{ + bidId: '2faedf3e89d125', + adUnitCode: 'ad-unit-instream', + mediaTypes: { video: { context: 'instream' } } + }] + } + } + }; + + const res = spec.interpretResponse(instreamResponse, req); + expect(res[0].vastUrl).to.equal('instream'); + }); + + it('should handle invalid JSON in bidderRequestData', () => { + const req = { + data: { + bidderRequestData: 'invalid-json' + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res).to.have.lengthOf(1); + // Should not crash, context will be undefined + }); + + it('should handle bidderRequestData without bids array', () => { + const req = { + data: { + bidderRequestData: encodeURIComponent(JSON.stringify({ refererInfo: {} })) + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res).to.have.lengthOf(1); + }); + + it('should handle empty bids array in bidderRequestData', () => { + const req = { + data: { + bidderRequestData: encodeURIComponent(JSON.stringify({ bids: [] })) + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res).to.have.lengthOf(1); + }); + + it('should find correct bid when multiple bids in bidderRequestData', () => { + const req = { + data: { + bidderRequestData: encodeURIComponent(JSON.stringify({ + bids: [ + { + bidId: 'OTHER_BID', + adUnitCode: 'other-unit', + mediaTypes: { video: { context: 'outstream' } } + }, + { + bidId: '2faedf3e89d124', + adUnitCode: 'ad-unit-outstream', + mediaTypes: { video: { context: 'outstream' } } + } + ] + })) + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res[0].vastXml).to.equal('outstream'); + expect(res[0].renderer).to.exist; + }); + + it('should handle missing mediaTypes in bid', () => { + const req = { + data: { + bidderRequestData: encodeURIComponent(JSON.stringify({ + bids: [{ + bidId: '2faedf3e89d124', + adUnitCode: 'ad-unit-outstream' + }] + })) + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + expect(res).to.have.lengthOf(1); + // Should not crash, context will be undefined + }); + }); + + // INTERPRET RESPONSE - EDGE CASES + + describe('interpretResponse - edge cases', () => { + it('should return empty array when serverResponse.body is empty object', () => { + const res = spec.interpretResponse({ body: {} }, { data: {} }); + expect(res).to.have.lengthOf(0); + }); + + it('should return empty array when serverResponse.body is null', () => { + const res = spec.interpretResponse({ body: null }, { data: {} }); + expect(res).to.have.lengthOf(0); + }); + + it('should return empty array when serverResponse.body is undefined', () => { + const res = spec.interpretResponse({ body: undefined }, { data: {} }); + expect(res).to.have.lengthOf(0); + }); + + it('should handle request without data object', () => { + const bannerResponse = { + body: { + slotBidId: '2faedf3e89d123', + ad: '
BANNER
', + cpm: 1, + mediaType: BANNER + } + }; + const res = spec.interpretResponse(bannerResponse, {}); + expect(res).to.have.lengthOf(1); + }); + + it('should handle video response without context (neither videoContext nor bidderRequestData)', () => { + const videoResponse = { + body: { + slotBidId: 'BID_VIDEO', + ad: 'video', + cpm: 2, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480 + } + }; + const req = { data: {} }; + const res = spec.interpretResponse(videoResponse, req); + expect(res).to.have.lengthOf(1); + // Neither vastUrl nor vastXml should be set + expect(res[0].vastUrl).to.not.exist; + expect(res[0].vastXml).to.not.exist; + }); + + it('should handle negative cpm', () => { + const responseNegativeCpm = { + body: { + slotBidId: '2faedf3e89d123', + ad: '
BANNER
', + cpm: -1, + mediaType: BANNER + } + }; + const req = { data: { q: 'dummy' } }; + const result = spec.interpretResponse(responseNegativeCpm, req); + expect(result[0].cpm).to.equal(0); + }); + }); + + // RENDERER TESTS + + describe('renderer functionality', () => { + it('should create renderer with correct configuration', () => { + const outstreamResponse = { + body: { + slotBidId: '2faedf3e89d124', + ad: 'outstream', + cpm: 2, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480, + rUrl: 'https://cdn/renderer.js', + advertiserDomains: ['example.com'] + } + }; + + const req = { + data: { + videoContext: 'outstream', + adUnitCode: 'ad-unit-outstream' + } + }; + + const res = spec.interpretResponse(outstreamResponse, req); + const renderer = res[0].renderer; + + expect(renderer).to.exist; + expect(renderer.url).to.equal('https://cdn/renderer.js'); + expect(renderer.id).to.equal('2faedf3e89d124'); + expect(typeof renderer.setRender).to.equal('function'); + }); + + it('should execute renderer callback when onetag is available', () => { + const outstreamResponse = { + body: { + slotBidId: '2faedf3e89d124', + ad: 'outstream', + cpm: 2, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480, + rUrl: 'https://cdn/test.xml', + advertiserDomains: ['example.com'] + } + }; + + const req = { + data: { + videoContext: 'outstream', + adUnitCode: 'ad-unit-outstream' + } + }; + + const originalOnetag = window.onetag; + let playerInitCalled = false; + + window.onetag = { + Player: { + init: function (config) { + playerInitCalled = true; + expect(config).to.exist; + expect(config.width).to.exist; + expect(config.height).to.exist; + expect(config.vastXml).to.exist; + expect(config.nodeId).to.exist; + } + } + }; + + try { + const res = spec.interpretResponse(outstreamResponse, req); + const renderer = res[0].renderer; + + renderer.loaded = true; + const renderFn = renderer._render; + expect(renderFn).to.exist; + + renderFn.call(renderer, { + renderer: renderer, + width: 640, + height: 480, + vastXml: 'outstream', + adUnitCode: 'ad-unit-outstream' + }); + + expect(playerInitCalled).to.equal(true); + } finally { + if (originalOnetag) { + window.onetag = originalOnetag; + } else { + delete window.onetag; + } + } + }); + + it('should handle renderer setRender errors gracefully', () => { + // This tests the try-catch block in createRenderer + const outstreamResponse = { + body: { + slotBidId: '2faedf3e89d124', + ad: 'outstream', + cpm: 2, + mediaType: VIDEO, + adWidth: 640, + adHeight: 480, + rUrl: 'https://cdn/test.xml', + advertiserDomains: ['example.com'] + } + }; + + const req = { + data: { + videoContext: 'outstream', + adUnitCode: 'ad-unit-outstream' + } + }; + + // Should not throw even if setRender fails + expect(() => { + spec.interpretResponse(outstreamResponse, req); + }).to.not.throw(); }); }); }); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index cee99ca8c38..6350b5e1de7 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -812,6 +812,136 @@ describe('InsticatorBidAdapter', function () { const data = JSON.parse(requests[0].data); expect(data.site.publisher).to.not.an('object'); }); + + it('should include publisherId as query parameter in endpoint URL', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: '86dd03a1-053f-4e3e-90e7-389070a0c62c' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + expect(requests[0].url).to.include('publisherId=86dd03a1-053f-4e3e-90e7-389070a0c62c'); + }); + + it('should not include publisherId query param if publisherId is not present', function () { + const tempBiddRequest = { + ...bidRequest, + } + // Ensure no publisherId in params + delete tempBiddRequest.params.publisherId; + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + expect(requests[0].url).to.not.include('publisherId'); + }); + + it('should not include publisherId query param if publisherId is empty string', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: '' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + expect(requests[0].url).to.not.include('publisherId'); + }); + + it('should include publisherId query param with custom endpoint URL', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: 'test-publisher-123', + bid_endpoint_request_url: 'https://custom.endpoint.com/v1/bid' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + expect(requests[0].url).to.equal('https://custom.endpoint.com/v1/bid?publisherId=test-publisher-123'); + }); + + // ORTB 2.6 Ad Pod video params tests + describe('Ad Pod video params', function () { + it('should include Ad Pod params when present in video mediaType', function () { + const adPodBidRequest = { + ...bidRequest, + mediaTypes: { + video: { + mimes: ['video/mp4', 'video/mpeg'], + w: 640, + h: 480, + podid: 'pod-123', + podseq: 1, + poddur: 300, + slotinpod: 1, + mincpmpersec: 0.02, + maxseq: 5, + rqddurs: [15, 30, 60], + }, + }, + }; + const requests = spec.buildRequests([adPodBidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + + expect(data.imp[0].video).to.have.property('podid', 'pod-123'); + expect(data.imp[0].video).to.have.property('podseq', 1); + expect(data.imp[0].video).to.have.property('poddur', 300); + expect(data.imp[0].video).to.have.property('slotinpod', 1); + expect(data.imp[0].video).to.have.property('mincpmpersec', 0.02); + expect(data.imp[0].video).to.have.property('maxseq', 5); + expect(data.imp[0].video).to.have.property('rqddurs').that.deep.equals([15, 30, 60]); + }); + + it('should not include invalid ORTB 2.6 video params', function () { + const adPodBidRequest = { + ...bidRequest, + mediaTypes: { + video: { + mimes: ['video/mp4'], + w: 640, + h: 480, + podid: '', // invalid - empty string + podseq: -1, // invalid - negative + poddur: 0, // invalid - zero + slotinpod: 5, // invalid - not in [-1, 0, 1, 2] + mincpmpersec: -0.5, // invalid - negative + maxseq: 0, // invalid - zero + rqddurs: [0, -15], // invalid - contains non-positive values + }, + }, + }; + const requests = spec.buildRequests([adPodBidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + + expect(data.imp[0].video).to.not.have.property('podid'); + expect(data.imp[0].video).to.not.have.property('podseq'); + expect(data.imp[0].video).to.not.have.property('poddur'); + expect(data.imp[0].video).to.not.have.property('slotinpod'); + expect(data.imp[0].video).to.not.have.property('mincpmpersec'); + expect(data.imp[0].video).to.not.have.property('maxseq'); + expect(data.imp[0].video).to.not.have.property('rqddurs'); + }); + + it('should validate slotinpod accepts valid values [-1, 0, 1, 2]', function () { + [-1, 0, 1, 2].forEach(slotValue => { + const adPodBidRequest = { + ...bidRequest, + mediaTypes: { + video: { + mimes: ['video/mp4'], + w: 640, + h: 480, + slotinpod: slotValue, + }, + }, + }; + const requests = spec.buildRequests([adPodBidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + + expect(data.imp[0].video).to.have.property('slotinpod', slotValue); + }); + }); + }); }); describe('interpretResponse', function () { @@ -929,7 +1059,7 @@ describe('InsticatorBidAdapter', function () { cpm: 0.5, currency: 'USD', netRevenue: true, - ttl: 60, + ttl: 60, // MIN(60, 300) = 60 - bid.exp is upper bound width: 300, height: 200, mediaType: 'banner', @@ -937,7 +1067,8 @@ describe('InsticatorBidAdapter', function () { adUnitCode: 'adunit-code-1', meta: { advertiserDomains: ['test1.com'], - test: 1 + test: 1, + seat: 'some-dsp' } }, { @@ -953,7 +1084,8 @@ describe('InsticatorBidAdapter', function () { meta: { advertiserDomains: [ 'test2.com' - ] + ], + seat: 'some-dsp' }, ad: 'adm2', adUnitCode: 'adunit-code-2', @@ -971,7 +1103,8 @@ describe('InsticatorBidAdapter', function () { meta: { advertiserDomains: [ 'test3.com' - ] + ], + seat: 'some-dsp' }, ad: 'adm3', adUnitCode: 'adunit-code-3', @@ -996,6 +1129,515 @@ describe('InsticatorBidAdapter', function () { delete response.body.seatbid; expect(spec.interpretResponse(response, bidRequests)).to.have.length(0); }); + + it('should return empty response for 204 No Content (undefined body)', function () { + const response = { body: undefined }; + expect(spec.interpretResponse(response, bidRequests)).to.have.length(0); + }); + + it('should return empty response for 204 No Content (null body)', function () { + const response = { body: null }; + expect(spec.interpretResponse(response, bidRequests)).to.have.length(0); + }); + + it('should return empty response for empty object body', function () { + const response = { body: {} }; + expect(spec.interpretResponse(response, bidRequests)).to.have.length(0); + }); + + // ORTB 2.6 Response Fields Tests + describe('ORTB 2.6 response fields', function () { + const ortb26BidRequests = { + method: 'POST', + url: 'https://ex.ingage.tech/v1/openrtb', + options: { + contentType: 'application/json', + withCredentials: true, + }, + data: '', + bidderRequest: { + bidderRequestId: '22edbae2733bf6', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + timeout: 300, + bids: [ + { + bidder: 'insticator', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + adUnitCode: 'adunit-code-1', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: 'bid1', + } + ] + } + }; + + it('should map category (cat) to meta.primaryCatId and meta.secondaryCatIds', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + cat: ['IAB1', 'IAB2-1', 'IAB3'], + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse.meta).to.have.property('primaryCatId', 'IAB1'); + expect(bidResponse.meta).to.have.property('secondaryCatIds').that.deep.equals(['IAB2-1', 'IAB3']); + }); + + it('should map single category without secondaryCatIds', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + cat: ['IAB1'], + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse.meta).to.have.property('primaryCatId', 'IAB1'); + expect(bidResponse.meta).to.not.have.property('secondaryCatIds'); + }); + + it('should map seat to meta.seat', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-seat-123', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + adomain: ['test.com'], + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse.meta).to.have.property('seat', 'dsp-seat-123'); + }); + + it('should map creative attributes (attr) to meta.attr', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + attr: [1, 2, 3], + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse.meta).to.have.property('attr').that.deep.equals([1, 2, 3]); + }); + + it('should map dealid to bidResponse.dealId', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + dealid: 'deal-abc-123', + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse).to.have.property('dealId', 'deal-abc-123'); + }); + + it('should map billing URL (burl) to bidResponse.burl', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + burl: 'https://billing.example.com/win?price=${AUCTION_PRICE}', + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse).to.have.property('burl', 'https://billing.example.com/win?price=${AUCTION_PRICE}'); + }); + + it('should map notice URL (nurl) to bidResponse.nurl', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + nurl: 'https://win.example.com/notify?price=${AUCTION_PRICE}', + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + expect(bidResponse).to.have.property('nurl', 'https://win.example.com/notify?price=${AUCTION_PRICE}'); + }); + + it('should map video duration (dur) to bidResponse.video.durationSeconds', function () { + const videoBidRequests = { + ...ortb26BidRequests, + bidderRequest: { + ...ortb26BidRequests.bidderRequest, + bids: [{ + ...ortb26BidRequests.bidderRequest.bids[0], + mediaTypes: { + video: { + mimes: ['video/mp4'], + playerSize: [[640, 480]], + } + } + }] + } + }; + + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 640, + h: 480, + adm: '', + dur: 30, + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, videoBidRequests)[0]; + + expect(bidResponse).to.have.property('video'); + expect(bidResponse.video).to.have.property('durationSeconds', 30); + }); + + it('should set video.durationSeconds and not set video.context for instream video', function () { + const instreamBidRequests = { + ...ortb26BidRequests, + bidderRequest: { + ...ortb26BidRequests.bidderRequest, + bids: [{ + ...ortb26BidRequests.bidderRequest.bids[0], + mediaTypes: { + video: { + mimes: ['video/mp4'], + playerSize: [[640, 480]], + context: 'instream', + } + } + }] + } + }; + + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 640, + h: 480, + adm: '', + dur: 30, + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, instreamBidRequests)[0]; + + expect(bidResponse).to.have.property('video'); + expect(bidResponse.video).to.have.property('durationSeconds', 30); + expect(bidResponse.video).to.not.have.property('context'); + }); + + it('should use MIN of bid.exp and BID_TTL for ttl (bid.exp is upper bound)', function () { + // When bid.exp (60) is less than BID_TTL (300), use 60 + const responseWithLowExp = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + exp: 60, + }] + }] + } + }; + const bidResponseLow = spec.interpretResponse(responseWithLowExp, ortb26BidRequests)[0]; + expect(bidResponseLow.ttl).to.equal(60); // MIN(60, 300) = 60 + + // When bid.exp (600) is greater than BID_TTL (300), use 300 + const responseWithHighExp = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + exp: 600, + }] + }] + } + }; + const bidResponseHigh = spec.interpretResponse(responseWithHighExp, ortb26BidRequests)[0]; + expect(bidResponseHigh.ttl).to.equal(300); // MIN(600, 300) = 300 + }); + + it('should default ttl to BID_TTL when bid.exp is not provided', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'adm1', + // no exp field + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.ttl).to.equal(300); // defaults to configTTL when no bid.exp + }); + + it('should include all ORTB 2.6 fields in a single response', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'full-dsp', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 2.5, + w: 300, + h: 250, + adm: 'adm1', + adomain: ['advertiser.com'], + cat: ['IAB1', 'IAB2'], + attr: [1, 2], + dealid: 'premium-deal', + burl: 'https://billing.example.com/win', + exp: 450, + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + + // Check all ORTB 2.6 fields + expect(bidResponse.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(bidResponse.meta.primaryCatId).to.equal('IAB1'); + expect(bidResponse.meta.secondaryCatIds).to.deep.equal(['IAB2']); + expect(bidResponse.meta.seat).to.equal('full-dsp'); + expect(bidResponse.meta.attr).to.deep.equal([1, 2]); + expect(bidResponse.dealId).to.equal('premium-deal'); + expect(bidResponse.burl).to.equal('https://billing.example.com/win'); + expect(bidResponse.ttl).to.equal(300); // MIN(450, 300) = 300 - bid.exp is upper bound + }); + + // Media Type Detection Tests + describe('media type detection', function () { + it('should detect video using mtype=2 (ORTB 2.6 standard)', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: 'some non-vast content', + mtype: 2, // video + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.mediaType).to.equal('video'); + }); + + it('should detect banner using mtype=1 (ORTB 2.6 standard)', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: '', // VAST content but mtype says banner + mtype: 1, // banner + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.mediaType).to.equal('banner'); + }); + + it('should detect video using case-insensitive VAST detection', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: '', // lowercase vast + // no mtype + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.mediaType).to.equal('video'); + }); + + it('should default to banner when no video signals present', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: '
banner ad
', + // no mtype, no VAST + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.mediaType).to.equal('banner'); + }); + + it('should detect banner when VAST-like content is inside script tag', function () { + const response = { + body: { + id: '22edbae2733bf6', + seatbid: [{ + seat: 'dsp-1', + bid: [{ + impid: 'bid1', + crid: 'crid1', + price: 1.0, + w: 300, + h: 250, + adm: '
banner
', + // no mtype + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.mediaType).to.equal('banner'); + }); + }); + }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/insuradsBidAdapter_spec.js b/test/spec/modules/insuradsBidAdapter_spec.js new file mode 100644 index 00000000000..0207477b8e4 --- /dev/null +++ b/test/spec/modules/insuradsBidAdapter_spec.js @@ -0,0 +1,737 @@ +import { expect } from 'chai'; +import { + spec, STORAGE, getInsurAdsLocalStorage, getGzipSetting, +} from 'modules/insuradsBidAdapter.js'; +import sinon from 'sinon'; +import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; +const sandbox = sinon.createSandbox(); + +describe('InsurAds bid adapter tests', () => { + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {}, + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page', + }, + uspConsent: '1112223334', + userId: { id5id: { uid: '1111' } }, + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com', + }], + }, + }; + + it('We test getGzipSettings', () => { + const output = getGzipSetting(); + expect(output).to.be.a('boolean'); + }); + + describe('isBidRequestValid()', () => { + let bannerBid; + beforeEach(() => { + bannerBid = { + bidder: 'nexx360', + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + adUnitCode: 'div-1', + transactionId: '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + sizes: [[300, 250], [300, 600]], + bidId: '4906582fc87d0c', + bidderRequestId: '332fda16002dbe', + auctionId: '98932591-c822-42e3-850e-4b3cf748d063', + } + }); + + it('We verify isBidRequestValid with unvalid adUnitName', () => { + bannerBid.params = { adUnitName: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with empty adUnitName', () => { + bannerBid.params = { adUnitName: '' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid adUnitPath', () => { + bannerBid.params = { adUnitPath: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid divId', () => { + bannerBid.params = { divId: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid unvalid allBids', () => { + bannerBid.params = { allBids: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with uncorrect tagid', () => { + bannerBid.params = { 'tagid': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with correct tagId', () => { + bannerBid.params = { 'tagId': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + + it('We verify isBidRequestValid with correct placement', () => { + bannerBid.params = { 'placement': 'testad' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + }); + + describe('getInsurAdsLocalStorage disabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => false); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getInsurAdsLocalStorage enabled but nothing', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => null); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(typeof output.nexx360Id).to.be.eql('string'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getInsurAdsLocalStorage enabled but wrong payload', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getInsurAdsLocalStorage enabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(output.nexx360Id).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data not set', () => { + before(() => { + 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', () => { + const output = getAmxId(STORAGE, 'nexx360'); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data set', () => { + before(() => { + 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', () => { + const output = getAmxId(STORAGE, 'nexx360'); + expect(output).to.be.eql('abcdef'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('buildRequests()', () => { + before(() => { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + }, + getBoundingClientRect() { return { width: 200, height: 250 }; } + }); + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => 'abcdef'); + }); + describe('We test with a multiple display bids', () => { + const sampleBids = [ + { + bidder: 'nexx360', + params: { + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + }, + ortb2Imp: { + ext: { + gpid: '/12345/nexx360/Homepage/HP/Header-Ad', + } + }, + adUnitCode: 'header-ad-1234', + transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', + sizes: [[300, 250], [300, 600]], + bidId: '44a2706ac3574', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + }, + { + bidder: 'nexx360', + params: { + placement: 'testPlacement', + allBids: true, + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 250]] + } + }, + + adUnitCode: 'div-2-abcd', + transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', + sizes: [[728, 90], [970, 250]], + bidId: '5ba94555219a03', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + } + ]; + const bidderRequest = { + bidderCode: 'nexx360', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + bidderRequestId: '359bf8a3c06b2e', + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + topmostLocation: 'https://test.nexx360.io/adapter/index.html', + location: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null, + page: 'https://test.nexx360.io/adapter/index.html', + domain: 'test.nexx360.io', + ref: null, + legacy: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + referer: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null + }, + }, + gdprConsent: { + gdprApplies: true, + consentString: 'CPhdLUAPhdLUAAKAsAENCmCsAP_AAE7AAAqIJFNd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7997BIiAaADgAJYBnwEeAJXAXmAwQBj4DtgHcgPBAeKBIgAA.YAAAAAAAAAAA', + } + }; + it('We perform a test with 2 display adunits', () => { + const displayBids = structuredClone(sampleBids); + displayBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; + const request = spec.buildRequests(displayBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + const expectedRequest = { + imp: [ + { + id: '44a2706ac3574', + banner: { + topframe: 0, + format: [ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + ], + }, + secure: 1, + tagid: 'header-ad-1234', + ext: { + adUnitCode: 'header-ad-1234', + gpid: '/12345/nexx360/Homepage/HP/Header-Ad', + divId: 'div-1', + dimensions: { + slotW: 200, + slotH: 250, + cssMaxW: '400px', + cssMaxH: '350px', + }, + nexx360: { + tagId: 'luvxjvgn', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + divId: 'div-1', + }, + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + }, + }, + { + id: '5ba94555219a03', + banner: { + topframe: 0, + format: [ + { w: 728, h: 90 }, + { w: 970, h: 250 }, + ], + }, + secure: 1, + tagid: 'div-2-abcd', + ext: { + adUnitCode: 'div-2-abcd', + divId: 'div-2-abcd', + nexx360: { + placement: 'testPlacement', + divId: 'div-2-abcd', + allBids: true, + }, + }, + }, + ], + id: requestContent.id, + test: 0, + ext: { + version: requestContent.ext.version, + source: 'prebid.js', + pageViewId: requestContent.ext.pageViewId, + bidderVersion: '7.1', + localStorage: { amxId: 'abcdef'}, + sessionId: requestContent.ext.sessionId, + requestCounter: 0, + }, + cur: [ + 'USD', + ], + user: { + ext: { + eids: [ + { + source: 'amxdt.net', + uids: [ + { + id: 'abcdef', + atype: 1, + } + ] + } + ] + } + }, + }; + expect(requestContent).to.be.eql(expectedRequest); + }); + + if (FEATURES.VIDEO) { + it('We perform a test with a multiformat adunit', () => { + const multiformatBids = structuredClone(sampleBids); + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const video = request.data.imp[0].video; + const expectedVideo = { + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + w: 640, + h: 480, + ext: { + playerSize: [640, 480], + context: 'outstream', + }, + }; + expect(video).to.eql(expectedVideo); + }); + + it('We perform a test with a instream adunit', () => { + const videoBids = structuredClone(sampleBids); + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); + } + }); + after(() => { + sandbox.restore() + }); + }); + + describe('We test interpretResponse', () => { + it('empty response', () => { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); + it('banner responses with adm', () => { + const response = { + body: { + id: 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '4427551302944024629', + impid: '226175918ebeda', + price: 1.5, + adomain: [ + 'http://prebid.org', + ], + crid: '98493581', + ssp: 'appnexus', + h: 600, + w: 300, + adm: '
TestAd
', + cat: [ + 'IAB3-1', + ], + ext: { + adUnitCode: 'div-1', + mediaType: 'banner', + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + ssp: 'appnexus', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + id: 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + cookies: [], + }, + }, + }; + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '226175918ebeda', + cpm: 1.5, + width: 300, + height: 600, + creativeId: '98493581', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'http://prebid.org', + ], + demandSource: 'appnexus', + }, + ad: '
TestAd
', + }]; + expect(output).to.eql(expectedOutput); + }); + + it('instream responses', () => { + const response = { + body: { + id: '2be64380-ba0c-405a-ab53-51f51c7bde51', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '8275140264321181514', + impid: '263cba3b8bfb72', + price: 5, + adomain: [ + 'appnexus.com', + ], + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'instream', + ssp: 'appnexus', + adUnitCode: 'video1', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '263cba3b8bfb72', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + }]; + expect(output).to.eql(expectedOutput); + }); + + it('outstream responses', () => { + const response = { + body: { + id: '40c23932-135e-4602-9701-ca36f8d80c07', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '1186971142548769361', + impid: '4ce809b61a3928', + price: 5, + adomain: [ + 'appnexus.com', + ], + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'outstream', + ssp: 'appnexus', + adUnitCode: 'div-1', + divId: 'div-1', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutut = [{ + requestId: '4ce809b61a3928', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + divId: 'div-1', + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + renderer: output[0].renderer, + }]; + expect(output).to.eql(expectedOutut); + }); + + it('native responses', () => { + const response = { + body: { + id: '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '6624930625245272225', + impid: '23e11d845514bb', + price: 10, + adomain: [ + 'prebid.org', + ], + crid: '97494204', + h: 1, + w: 1, + cat: [ + 'IAB3-1', + ], + ext: { + mediaType: 'native', + ssp: 'appnexus', + adUnitCode: '/19968336/prebid_native_example_1', + }, + adm: '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}', + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectOutput = [{ + requestId: '23e11d845514bb', + cpm: 10, + width: 1, + height: 1, + creativeId: '97494204', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'native', + meta: { + advertiserDomains: [ + 'prebid.org', + ], + demandSource: 'appnexus', + }, + native: { + ortb: { + ver: '1.2', + assets: [ + { + id: 1, + img: { + url: 'https://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg', + w: 989, + h: 742, + ext: { + appnexus: { + prevent_crop: 0, + }, + }, + }, + }, + { + id: 0, + title: { + text: 'This is a Prebid Native Creative', + }, + }, + { + id: 2, + data: { + value: 'Prebid.org', + }, + }, + ], + link: { + url: 'https://ams3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA./bcr=AAAAAAAA8D8=/pp=${AUCTION_PRICE}/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA/cca=OTMyNSNBTVMzOjYxMzU=/bn=97062/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html', + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://ams3-ib.adnxs.com/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}', + }, + ], + }, + }, + }]; + expect(output).to.eql(expectOutput); + }); + }); + + describe('getUserSyncs()', () => { + const response = { body: { cookies: [] } }; + it('Verifies user sync without cookie in bid response', () => { + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with cookies in bid response', () => { + response.body.ext = { + cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + }; + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); + const expectedSyncs = [{ type: 'image', url: 'http://www.cookie.sync.org/' }]; + expect(syncs).to.eql(expectedSyncs); + }); + it('Verifies user sync with no bid response', () => { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with no bid body response', () => { + let syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + }); +}); diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 664c6041ec8..8389ca4a644 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -1,45 +1,108 @@ -import { expect } from 'chai'; -import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js'; -import * as utils from 'src/utils.js'; -import { server } from 'test/mocks/xhr.js'; -import { config } from 'src/config.js'; -import { EVENTS } from 'src/constants.js'; -import * as events from 'src/events.js'; -import { getStorageManager } from 'src/storageManager.js'; -import sinon from 'sinon'; -import { REPORTER_ID, preparePayload, restoreReportList } from '../../../modules/intentIqAnalyticsAdapter.js'; -import { FIRST_PARTY_KEY, PREBID, VERSION } from '../../../libraries/intentIqConstants/intentIqConstants.js'; -import * as detectBrowserUtils from '../../../libraries/intentIqUtils/detectBrowserUtils.js'; -import { getReferrer, appendVrrefAndFui } from '../../../libraries/intentIqUtils/getRefferer.js'; -import { gppDataHandler, uspDataHandler, gdprDataHandler } from '../../../src/consentHandler.js'; - +import { expect } from "chai"; +import iiqAnalyticsAnalyticsAdapter from "modules/intentIqAnalyticsAdapter.js"; +import * as utils from "src/utils.js"; +import { server } from "test/mocks/xhr.js"; +import { config } from "src/config.js"; +import { EVENTS } from "src/constants.js"; +import * as events from "src/events.js"; +import { getGlobal } from "../../../src/prebidGlobal.js"; +import sinon from "sinon"; +import { + REPORTER_ID, + preparePayload, + restoreReportList, +} from "../../../modules/intentIqAnalyticsAdapter.js"; +import { + FIRST_PARTY_KEY, + PREBID, + VERSION, + WITHOUT_IIQ, + WITH_IIQ, + AB_CONFIG_SOURCE, +} from "../../../libraries/intentIqConstants/intentIqConstants.js"; +import * as detectBrowserUtils from "../../../libraries/intentIqUtils/detectBrowserUtils.js"; +import { + getCurrentUrl, + appendVrrefAndFui, +} from "../../../libraries/intentIqUtils/getRefferer.js"; +import { + gppDataHandler, + uspDataHandler, + gdprDataHandler, +} from "../../../src/consentHandler.js"; + +let getConfigStub; +let userIdConfigForTest; const partner = 10; -const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; +const identityName = `iiq_identity_${partner}` +const defaultIdentityObject = { + firstPartyData: { + pcid: "f961ffb1-a0e1-4696-a9d2-a21d815bd344", + pcidDate: 1762527405808, + uspString: "undefined", + gppString: "undefined", + gdprString: "", + date: Date.now(), + sCal: Date.now() - 36000, + isOptedOut: false, + pid: "profile", + dbsaved: "true" + }, + partnerData: { + abTestUuid: "abTestUuid", + adserverDeviceType: 1, + clientType: 2, + cttl: 43200000, + date: Date.now(), + profile: "profile", + wsrvcll: true, + }, + clientHints: JSON.stringify({ + 0: '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', + 1: "?0", + 2: '"macOS"', + 3: '"arm"', + 4: '"64"', + 6: '"15.6.1"', + 7: "?0", + 8: '"Chromium";v="142.0.7444.60", "Google Chrome";v="142.0.7444.60", "Not_A Brand";v="99.0.0.0"', + }), +}; +const regionCases = [ + { + name: 'default (no region)', + region: undefined, + expectedEndpoint: 'https://reports.intentiq.com/report' + }, + { + name: 'apac', + region: 'apac', + expectedEndpoint: 'https://reports-apac.intentiq.com/report' + }, + { + name: 'emea', + region: 'emea', + expectedEndpoint: 'https://reports-emea.intentiq.com/report' + }, + { + name: 'gdpr', + region: 'gdpr', + expectedEndpoint: 'https://reports-gdpr.intentiq.com/report' + } +] const version = VERSION; -const REPORT_ENDPOINT = 'https://reports.intentiq.com/report'; -const REPORT_ENDPOINT_GDPR = 'https://reports-gdpr.intentiq.com/report'; -const REPORT_SERVER_ADDRESS = 'https://test-reports.intentiq.com/report'; - -const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); +const REPORT_ENDPOINT = "https://reports.intentiq.com/report"; +const REPORT_ENDPOINT_GDPR = "https://reports-gdpr.intentiq.com/report"; +const REPORT_SERVER_ADDRESS = "https://test-reports.intentiq.com/report"; -const randomVal = () => Math.floor(Math.random() * 100000) + 1 +const randomVal = () => Math.floor(Math.random() * 100000) + 1; -const getUserConfig = () => [ - { - 'name': 'intentIqId', - 'params': { - 'partner': partner, - 'unpack': null, - 'manualWinReportEnabled': false, - }, - 'storage': { - 'type': 'html5', - 'name': 'intentIqId', - 'expires': 60, - 'refreshInSeconds': 14400 - } +const getDefaultConfig = () => { + return { + partner, + manualWinReportEnabled: false, } -]; +} const getUserConfigWithReportingServerAddress = () => [ { @@ -58,46 +121,49 @@ const getUserConfigWithReportingServerAddress = () => [ ]; const getWonRequest = () => ({ - '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-' + randomVal(), - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 5, - 'currency': 'USD', - 'ttl': 300, - 'referrer': '', - 'adapterCode': 'pubmatic', - 'originalCpm': 5, - 'originalCurrency': 'USD', - 'responseTimestamp': 1669644710345, - 'requestTimestamp': 1669644710109, - 'bidder': 'testbidder', - 'timeToRespond': 236, - 'pbLg': '5.00', - 'pbMg': '5.00', - 'pbHg': '5.00', - 'pbAg': '5.00', - 'pbDg': '5.00', - 'pbCg': '', - 'size': '728x90', - 'status': 'rendered' + 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-" + randomVal(), + mediaType: "banner", + source: "client", + cpm: 5, + currency: "USD", + ttl: 300, + referrer: "", + adapterCode: "pubmatic", + originalCpm: 5, + originalCurrency: "USD", + responseTimestamp: 1669644710345, + requestTimestamp: 1669644710109, + bidder: "testbidder", + timeToRespond: 236, + pbLg: "5.00", + pbMg: "5.00", + pbHg: "5.00", + pbAg: "5.00", + pbDg: "5.00", + pbCg: "", + size: "728x90", + status: "rendered", }); -const enableAnalyticWithSpecialOptions = (options) => { - iiqAnalyticsAnalyticsAdapter.disableAnalytics() +const enableAnalyticWithSpecialOptions = (receivedOptions) => { + iiqAnalyticsAnalyticsAdapter.disableAnalytics(); iiqAnalyticsAnalyticsAdapter.enableAnalytics({ - provider: 'iiqAnalytics', - options - }) -} + provider: "iiqAnalytics", + options: { + ...getDefaultConfig(), + ...receivedOptions + }, + }); +}; -describe('IntentIQ tests all', function () { +describe("IntentIQ tests all", function () { let logErrorStub; let getWindowSelfStub; let getWindowTopStub; @@ -105,12 +171,13 @@ describe('IntentIQ tests all', function () { let detectBrowserStub; beforeEach(function () { - logErrorStub = sinon.stub(utils, 'logError'); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(getUserConfig()); - sinon.stub(events, 'getEvents').returns([]); - iiqAnalyticsAnalyticsAdapter.enableAnalytics({ - provider: 'iiqAnalytics', - }); + logErrorStub = sinon.stub(utils, "logError"); + sinon.stub(events, "getEvents").returns([]); + + if (config.getConfig && config.getConfig.restore) { + config.getConfig.restore(); + } + iiqAnalyticsAnalyticsAdapter.initOptions = { lsValueInitialized: false, partner: null, @@ -121,21 +188,26 @@ describe('IntentIQ tests all', function () { eidl: null, lsIdsInitialized: false, manualWinReportEnabled: false, - domainName: null + domainName: null, }; + iiqAnalyticsAnalyticsAdapter.enableAnalytics({ + provider: "iiqAnalytics", + options: getDefaultConfig() + }); if (iiqAnalyticsAnalyticsAdapter.track.restore) { iiqAnalyticsAnalyticsAdapter.track.restore(); } - sinon.spy(iiqAnalyticsAnalyticsAdapter, 'track'); + sinon.spy(iiqAnalyticsAnalyticsAdapter, "track"); + window[identityName] = utils.deepClone(defaultIdentityObject); }); afterEach(function () { logErrorStub.restore(); + if (getConfigStub && getConfigStub.restore) getConfigStub.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) { @@ -143,20 +215,15 @@ describe('IntentIQ tests all', function () { } localStorage.clear(); server.reset(); + delete window[`iiq_identity_${partner}`] }); - it('should send POST request with payload in request body if reportMethod is POST', function () { + it("should send POST request with payload in request body if reportMethod is POST", function () { enableAnalyticWithSpecialOptions({ - reportMethod: 'POST' - }) - const [userConfig] = getUserConfig(); + reportMethod: "POST", + }); const wonRequest = getWonRequest(); - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); - - localStorage.setItem(FIRST_PARTY_KEY, defaultData); - events.emit(EVENTS.BID_WON, wonRequest); const request = server.requests[0]; @@ -165,25 +232,21 @@ describe('IntentIQ tests all', function () { const expectedData = preparePayload(wonRequest); const expectedPayload = `["${btoa(JSON.stringify(expectedData))}"]`; - expect(request.method).to.equal('POST'); + expect(request.method).to.equal("POST"); expect(request.requestBody).to.equal(expectedPayload); }); - it('should send GET request with payload in query string if reportMethod is NOT provided', function () { - const [userConfig] = getUserConfig(); + it("should send GET request with payload in query string if reportMethod is NOT provided", function () { const wonRequest = getWonRequest(); - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, wonRequest); const request = server.requests[0]; - expect(request.method).to.equal('GET'); + expect(request.method).to.equal("GET"); const url = new URL(request.url); - const payloadEncoded = url.searchParams.get('payload'); + const payloadEncoded = url.searchParams.get("payload"); const decoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); restoreReportList(); @@ -194,250 +257,338 @@ describe('IntentIQ tests all', function () { expect(decoded.prebidAuctionId).to.equal(expected.prebidAuctionId); }); - it('IIQ Analytical Adapter bid win report', function () { - localStorage.setItem(FIRST_PARTY_KEY, defaultData); - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876' }); + it("IIQ Analytical Adapter bid win report", function () { + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns({ href: "http://localhost:9876" }); const expectedVrref = getWindowLocationStub().href; events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; const parsedUrl = new URL(request.url); - const vrref = parsedUrl.searchParams.get('vrref'); - expect(request.url).to.contain(REPORT_ENDPOINT + '?pid=' + partner + '&mct=1'); + const vrref = parsedUrl.searchParams.get("vrref"); + expect(request.url).to.contain( + REPORT_ENDPOINT + "?pid=" + partner + "&mct=1" + ); expect(request.url).to.contain(`&jsver=${version}`); - expect(`&vrref=${decodeURIComponent(vrref)}`).to.contain(`&vrref=${expectedVrref}`); - expect(request.url).to.contain('&payload='); - expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + expect(`&vrref=${decodeURIComponent(vrref)}`).to.contain( + `&vrref=${expectedVrref}` + ); + expect(request.url).to.contain("&payload="); + expect(request.url).to.contain( + "iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344" + ); }); - it('should include adType in payload when present in BID_WON event', function () { - localStorage.setItem(FIRST_PARTY_KEY, defaultData); - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); - const bidWonEvent = { ...getWonRequest(), mediaType: 'video' }; + it("should include adType in payload when present in BID_WON event", function () { + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns({ href: "http://localhost:9876/" }); + const bidWonEvent = { ...getWonRequest(), mediaType: "video" }; events.emit(EVENTS.BID_WON, bidWonEvent); const request = server.requests[0]; const urlParams = new URL(request.url); - const payloadEncoded = urlParams.searchParams.get('payload'); + const payloadEncoded = urlParams.searchParams.get("payload"); const payloadDecoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); expect(server.requests.length).to.be.above(0); - expect(payloadDecoded).to.have.property('adType', bidWonEvent.mediaType); + expect(payloadDecoded).to.have.property("adType", bidWonEvent.mediaType); }); - it('should include adType in payload when present in reportExternalWin event', function () { - enableAnalyticWithSpecialOptions({ manualWinReportEnabled: true }) - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); - const externalWinEvent = { cpm: 1, currency: 'USD', adType: 'banner' }; - const [userConfig] = getUserConfig(); - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); - - const partnerId = userConfig.params.partner; + it("should include adType in payload when present in reportExternalWin event", function () { + enableAnalyticWithSpecialOptions({ manualWinReportEnabled: true }); + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns({ href: "http://localhost:9876/" }); + const externalWinEvent = { cpm: 1, currency: "USD", adType: "banner" }; events.emit(EVENTS.BID_REQUESTED); - window[`intentIqAnalyticsAdapter_${partnerId}`].reportExternalWin(externalWinEvent); + window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin( + externalWinEvent + ); const request = server.requests[0]; const urlParams = new URL(request.url); - const payloadEncoded = urlParams.searchParams.get('payload'); + const payloadEncoded = urlParams.searchParams.get("payload"); const payloadDecoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); expect(server.requests.length).to.be.above(0); - expect(payloadDecoded).to.have.property('adType', externalWinEvent.adType); + expect(payloadDecoded).to.have.property("adType", externalWinEvent.adType); }); - it('should send report to report-gdpr address if gdpr is detected', function () { - const gppStub = sinon.stub(gppDataHandler, 'getConsentData').returns({ gppString: '{"key1":"value1","key2":"value2"}' }); - const uspStub = sinon.stub(uspDataHandler, 'getConsentData').returns('1NYN'); - const gdprStub = sinon.stub(gdprDataHandler, 'getConsentData').returns({ consentString: 'gdprConsent' }); + it("should get pos from pbjs.adUnits when BID_WON has no pos", function () { + const pbjs = getGlobal(); + const prevAdUnits = pbjs.adUnits; - events.emit(EVENTS.BID_WON, getWonRequest()); + pbjs.adUnits = Array.isArray(pbjs.adUnits) ? pbjs.adUnits : []; + pbjs.adUnits.push({ code: "myVideoAdUnit", mediaTypes: { video: { pos: 777 } } }); + + enableAnalyticWithSpecialOptions({ manualWinReportEnabled: false }); + + events.emit(EVENTS.BID_WON, { + ...getWonRequest(), + adUnitCode: "myVideoAdUnit", + mediaType: "video" + }); - expect(server.requests.length).to.be.above(0); const request = server.requests[0]; + const payloadEncoded = new URL(request.url).searchParams.get("payload"); + const payloadDecoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); - expect(request.url).to.contain(REPORT_ENDPOINT_GDPR); - gppStub.restore(); - uspStub.restore(); - gdprStub.restore(); + expect(payloadDecoded.pos).to.equal(777); + + pbjs.adUnits = prevAdUnits; + }); + + it("should get pos from reportExternalWin when present", function () { + enableAnalyticWithSpecialOptions({ manualWinReportEnabled: true }); + + const winPos = 999; + + window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({ + adUnitCode: "myVideoAdUnit", + bidderCode: "appnexus", + cpm: 1.5, + currency: "USD", + mediaType: "video", + size: "300x250", + status: "rendered", + auctionId: "auc123", + pos: winPos + }); + + const request = server.requests[0]; + const payloadEncoded = new URL(request.url).searchParams.get("payload"); + const payloadDecoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); + + expect(payloadDecoded.pos).to.equal(winPos); }); - it('should initialize with default configurations', function () { - expect(iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized).to.be.false; + it("should initialize with default configurations", function () { + expect(iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized).to.be + .false; }); - it('should handle BID_WON event with group configuration from local storage', function () { - localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); - const expectedVrref = encodeURIComponent('http://localhost:9876/'); + it("should handle BID_WON event with group configuration from local storage", function () { + window[`iiq_identity_${partner}`].firstPartyData = { + ...window[`iiq_identity_${partner}`].firstPartyData, + group: "B", + }; + + const expectedVrref = encodeURIComponent("http://localhost:9876/"); events.emit(EVENTS.BID_WON, getWonRequest()); 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( + "https://reports.intentiq.com/report?pid=" + partner + "&mct=1" + ); expect(request.url).to.contain(`&jsver=${version}`); expect(request.url).to.contain(`&vrref=${expectedVrref}`); - expect(request.url).to.contain('iiqid=testpcid'); }); - it('should handle BID_WON event with default group configuration', function () { - localStorage.setItem(FIRST_PARTY_KEY, defaultData); - const defaultDataObj = JSON.parse(defaultData) + it("should handle BID_WON event with default group configuration", function () { + const spdData = "server provided data"; + const expectedSpdEncoded = encodeURIComponent(spdData); + window[identityName].partnerData.spd = spdData; const wonRequest = getWonRequest(); events.emit(EVENTS.BID_WON, wonRequest); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; - restoreReportList() + restoreReportList(); const dataToSend = preparePayload(wonRequest); const base64String = btoa(JSON.stringify(dataToSend)); const payload = encodeURIComponent(JSON.stringify([base64String])); - const expectedUrl = appendVrrefAndFui(REPORT_ENDPOINT + - `?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&source=pbjs&uh=&gdpr=0`, iiqAnalyticsAnalyticsAdapter.initOptions.domainName + const expectedUrl = appendVrrefAndFui( + REPORT_ENDPOINT + + `?pid=${partner}&mct=1&iiqid=${defaultIdentityObject.firstPartyData.pcid}&agid=${REPORTER_ID}&jsver=${version}&source=pbjs&uh=${encodeURIComponent(window[identityName].clientHints)}&gdpr=0&spd=${expectedSpdEncoded}`, + iiqAnalyticsAnalyticsAdapter.initOptions.domainName ); const urlWithPayload = expectedUrl + `&payload=${payload}`; expect(request.url).to.equal(urlWithPayload); - expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) + expect(dataToSend.pcid).to.equal(defaultIdentityObject.firstPartyData.pcid); }); - it('should send CMP data in report if available', function () { - const uspData = '1NYN'; + it("should send CMP data in report if available", function () { + const uspData = "1NYN"; const gppData = { gppString: '{"key1":"value1","key2":"value2"}' }; - const gdprData = { consentString: 'gdprConsent' }; - - const gppStub = sinon.stub(gppDataHandler, 'getConsentData').returns(gppData); - const uspStub = sinon.stub(uspDataHandler, 'getConsentData').returns(uspData); - const gdprStub = sinon.stub(gdprDataHandler, 'getConsentData').returns(gdprData); - - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); + const gdprData = { consentString: "gdprConsent" }; + + const gppStub = sinon + .stub(gppDataHandler, "getConsentData") + .returns(gppData); + const uspStub = sinon + .stub(uspDataHandler, "getConsentData") + .returns(uspData); + const gdprStub = sinon + .stub(gdprDataHandler, "getConsentData") + .returns(gdprData); + + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns({ href: "http://localhost:9876/" }); events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; - expect(request.url).to.contain(`&us_privacy=${encodeURIComponent(uspData)}`); - expect(request.url).to.contain(`&gpp=${encodeURIComponent(gppData.gppString)}`); - expect(request.url).to.contain(`&gdpr_consent=${encodeURIComponent(gdprData.consentString)}`); + expect(request.url).to.contain( + `&us_privacy=${encodeURIComponent(uspData)}` + ); + expect(request.url).to.contain( + `&gpp=${encodeURIComponent(gppData.gppString)}` + ); + expect(request.url).to.contain( + `&gdpr_consent=${encodeURIComponent(gdprData.consentString)}` + ); expect(request.url).to.contain(`&gdpr=1`); gppStub.restore(); uspStub.restore(); gdprStub.restore(); }); - it('should not send request if manualWinReportEnabled is true', function () { - iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; - events.emit(EVENTS.BID_WON, getWonRequest()); - expect(server.requests.length).to.equal(1); + regionCases.forEach(({ name, region, expectedEndpoint }) => { + it(`should send request to region-specific report endpoint when region is "${name}"`, function () { + userIdConfigForTest = getUserConfigWithReportingServerAddress(); + getConfigStub = sinon.stub(config, "getConfig"); + getConfigStub.withArgs("userSync.userIds").callsFake(() => userIdConfigForTest); + + enableAnalyticWithSpecialOptions({ region }); + + events.emit(EVENTS.BID_WON, getWonRequest()); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain(expectedEndpoint); + }); }); - 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}'); + it("should not send request if manualWinReportEnabled is true", function () { + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; events.emit(EVENTS.BID_WON, getWonRequest()); - expect(iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs).to.equal('testpcid'); - expect(iiqAnalyticsAnalyticsAdapter.initOptions.eidl).to.equal(10); - expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('A'); + expect(server.requests.length).to.equal(0); }); - 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"}'); + it("should handle initialization values from local storage", function () { + window[`iiq_identity_${partner}`].actualABGroup = WITHOUT_IIQ; + events.emit(EVENTS.BID_WON, getWonRequest()); - expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('B'); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal( + WITHOUT_IIQ + ); expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; }); - it('should handle reportExternalWin', function () { + it("should handle reportExternalWin", function () { events.emit(EVENTS.BID_REQUESTED); iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = false; - localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); - localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); - expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin).to.be.a('function'); - expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({ cpm: 1, currency: 'USD' })).to.equal(false); + expect( + window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin + ).to.be.a("function"); + expect( + window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({ + cpm: 1, + currency: "USD", + }) + ).to.equal(false); }); - it('should return window.location.href when window.self === window.top', function () { + 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/'); + 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 = getCurrentUrl(); + expect(referrer).to.equal("http://localhost:9876/"); }); - it('should return window.top.location.href when window.self !== window.top and access is successful', function () { + 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/' } }); + getWindowSelfStub = sinon.stub(utils, "getWindowSelf").returns({}); + getWindowTopStub = sinon + .stub(utils, "getWindowTop") + .returns({ location: { href: "http://example.com/" } }); - const referrer = getReferrer(); + const referrer = getCurrentUrl(); - expect(referrer).to.equal('http://example.com/'); + 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 () { + 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')); + getWindowSelfStub = sinon.stub(utils, "getWindowSelf").returns({}); + getWindowTopStub = sinon + .stub(utils, "getWindowTop") + .throws(new Error("Access denied")); - const referrer = getReferrer(); - expect(referrer).to.equal(''); + const referrer = getCurrentUrl(); + expect(referrer).to.equal(""); expect(logErrorStub.calledOnce).to.be.true; - expect(logErrorStub.firstCall.args[0]).to.contain('Error accessing location: Error: Access denied'); + 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 = [...getUserConfig()]; - 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'); + it("should not send request if the browser is in blacklist (chrome)", function () { + enableAnalyticWithSpecialOptions({ + browserBlackList: "ChrOmE" + }) + detectBrowserStub = sinon + .stub(detectBrowserUtils, "detectBrowser") + .returns("chrome"); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.equal(0); }); - it('should send request if the browser is not in blacklist (safari)', function () { - const USERID_CONFIG_BROWSER = [...getUserConfig()]; - USERID_CONFIG_BROWSER[0].params.browserBlackList = 'chrome,firefox'; + it("should send request if the browser is not in blacklist (safari)", function () { + enableAnalyticWithSpecialOptions({ + browserBlackList: "chrome,firefox" + }) - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG_BROWSER); - detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('safari'); + detectBrowserStub = sinon + .stub(detectBrowserUtils, "detectBrowser") + .returns("safari"); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, getWonRequest()); 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( + `https://reports.intentiq.com/report?pid=${partner}&mct=1` + ); expect(request.url).to.contain(`&jsver=${version}`); - expect(request.url).to.contain(`&vrref=${encodeURIComponent('http://localhost:9876/')}`); - expect(request.url).to.contain('&payload='); - expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + expect(request.url).to.contain( + `&vrref=${encodeURIComponent("http://localhost:9876/")}` + ); + expect(request.url).to.contain("&payload="); + expect(request.url).to.contain( + "iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344" + ); }); - it('should send request in reportingServerAddress no gdpr', function () { - const USERID_CONFIG_BROWSER = [...getUserConfigWithReportingServerAddress()]; - 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'); - enableAnalyticWithSpecialOptions({ reportingServerAddress: REPORT_SERVER_ADDRESS }) + it("should send request in reportingServerAddress no gdpr", function () { + detectBrowserStub = sinon + .stub(detectBrowserUtils, "detectBrowser") + .returns("safari"); + enableAnalyticWithSpecialOptions({ + reportingServerAddress: REPORT_SERVER_ADDRESS, + browserBlackList: "chrome,firefox" + }); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, getWonRequest()); expect(server.requests.length).to.be.above(0); @@ -445,9 +596,7 @@ describe('IntentIQ tests all', function () { expect(request.url).to.contain(REPORT_SERVER_ADDRESS); }); - it('should include source parameter in report URL', function () { - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify(defaultData)); - + it("should include source parameter in report URL", function () { events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; @@ -455,64 +604,82 @@ describe('IntentIQ tests all', function () { expect(request.url).to.include(`&source=${PREBID}`); }); - it('should use correct key if siloEnabled is true', function () { - const siloEnabled = true; - const USERID_CONFIG = [...getUserConfig()]; - USERID_CONFIG[0].params.siloEnabled = siloEnabled; - - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG); + it("should send additionalParams in report if valid and small enough", function () { + enableAnalyticWithSpecialOptions({ + additionalParams: [ + { + parameterName: "general", + parameterValue: "Lee", + destination: [0, 0, 1], + }, + ] + }) - localStorage.setItem(FIRST_PARTY_KEY, `${FIRST_PARTY_KEY}${siloEnabled ? '_p_' + partner : ''}`); events.emit(EVENTS.BID_WON, getWonRequest()); - expect(server.requests.length).to.be.above(0); const request = server.requests[0]; - expect(request.url).to.contain(REPORT_ENDPOINT + '?pid=' + partner + '&mct=1'); + expect(request.url).to.include("general=Lee"); }); - it('should send additionalParams in report if valid and small enough', function () { - const userConfig = getUserConfig(); - userConfig[0].params.additionalParams = [{ - parameterName: 'general', - parameterValue: 'Lee', - destination: [0, 0, 1] - }]; + it("should include domainName in both query and payload when fullUrl is empty (cross-origin)", function () { + const domainName = "mydomain-frame.com"; - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(userConfig); + enableAnalyticWithSpecialOptions({ domainName }); + + getWindowTopStub = sinon.stub(utils, "getWindowTop").throws(new Error("cross-origin")); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; - expect(request.url).to.include('general=Lee'); + + // Query contain vrref=domainName + const parsedUrl = new URL(request.url); + const vrrefParam = parsedUrl.searchParams.get("vrref"); + + // Payload contain vrref=domainName + const payloadEncoded = parsedUrl.searchParams.get("payload"); + const payloadDecoded = JSON.parse(atob(JSON.parse(payloadEncoded)[0])); + + expect(server.requests.length).to.be.above(0); + expect(vrrefParam).to.not.equal(null); + expect(decodeURIComponent(vrrefParam)).to.equal(domainName); + expect(parsedUrl.searchParams.get("fui")).to.equal("1"); + + expect(payloadDecoded).to.have.property("vrref"); + expect(decodeURIComponent(payloadDecoded.vrref)).to.equal(domainName); + + restoreReportList(); }); - it('should not send additionalParams in report if value is too large', function () { - const longVal = 'x'.repeat(5000000); - const userConfig = getUserConfig(); - userConfig[0].params.additionalParams = [{ - parameterName: 'general', - parameterValue: longVal, - destination: [0, 0, 1] - }]; + it("should not send additionalParams in report if value is too large", function () { + const longVal = "x".repeat(5000000); - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(userConfig); + enableAnalyticWithSpecialOptions({ + additionalParams: [ + { + parameterName: "general", + parameterValue: longVal, + destination: [0, 0, 1], + }, + ] + }) - localStorage.setItem(FIRST_PARTY_KEY, defaultData); events.emit(EVENTS.BID_WON, getWonRequest()); const request = server.requests[0]; - expect(request.url).not.to.include('general'); + expect(request.url).not.to.include("general"); }); - it('should include spd parameter from LS in report URL', function () { - const spdObject = { foo: 'bar', value: 42 }; + + it("should include spd parameter from LS in report URL", function () { + const spdObject = { foo: "bar", value: 42 }; const expectedSpdEncoded = encodeURIComponent(JSON.stringify(spdObject)); + window[identityName].firstPartyData.spd = + JSON.stringify(spdObject); + window[identityName].partnerData.spd = spdObject; - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ ...defaultData, spd: spdObject })); - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns({ href: "http://localhost:9876/" }); events.emit(EVENTS.BID_WON, getWonRequest()); @@ -522,12 +689,14 @@ describe('IntentIQ tests all', function () { expect(request.url).to.include(`&spd=${expectedSpdEncoded}`); }); - it('should include spd parameter string from LS in report URL', function () { - const spdObject = 'server provided data'; - const expectedSpdEncoded = encodeURIComponent(spdObject); + it("should include spd parameter string from LS in report URL", function () { + const spdData = "server provided data"; + const expectedSpdEncoded = encodeURIComponent(spdData); + window[identityName].partnerData.spd = spdData; - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ ...defaultData, spd: spdObject })); - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns({ href: "http://localhost:9876/" }); events.emit(EVENTS.BID_WON, getWonRequest()); @@ -537,7 +706,7 @@ describe('IntentIQ tests all', function () { expect(request.url).to.include(`&spd=${expectedSpdEncoded}`); }); - describe('GAM prediction reporting', function () { + describe("GAM prediction reporting", function () { function createMockGAM() { const listeners = {}; return { @@ -545,86 +714,96 @@ describe('IntentIQ tests all', function () { pubads: () => ({ addEventListener: (name, cb) => { listeners[name] = cb; - } + }, }), - _listeners: listeners + _listeners: listeners, }; } - function withConfigGamPredict(gamObj) { - const [userConfig] = getUserConfig(); - userConfig.params.gamObjectReference = gamObj; - userConfig.params.gamPredictReporting = true; - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); - } - - it('should subscribe to GAM and send report on slotRenderEnded without prior bidWon', function () { + it("should subscribe to GAM and send report on slotRenderEnded without prior bidWon", function () { const gam = createMockGAM(); - withConfigGamPredict(gam); + + enableAnalyticWithSpecialOptions({ + gamObjectReference: gam + }) // enable subscription by LS flag - localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({ gpr: true })); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); + window[`iiq_identity_${partner}`].partnerData.gpr = true; // provide recent auctionEnd with matching bid to enrich payload events.getEvents.restore(); - sinon.stub(events, 'getEvents').returns([ + sinon.stub(events, "getEvents").returns([ { - eventType: 'auctionEnd', args: { - auctionId: 'auc-1', - adUnitCodes: ['ad-unit-1'], - bidsReceived: [{ bidder: 'pubmatic', adUnitCode: 'ad-unit-1', cpm: 1, currency: 'USD', originalCpm: 1, originalCurrency: 'USD', status: 'rendered' }] - } - } + eventType: "auctionEnd", + args: { + auctionId: "auc-1", + adUnitCodes: ["ad-unit-1"], + bidsReceived: [ + { + bidder: "pubmatic", + adUnitCode: "ad-unit-1", + cpm: 1, + currency: "USD", + originalCpm: 1, + originalCurrency: "USD", + status: "rendered", + }, + ], + }, + }, ]); // trigger adapter to subscribe events.emit(EVENTS.BID_REQUESTED); // execute GAM cmd to register listener - gam.cmd.forEach(fn => fn()); + gam.cmd.forEach((fn) => fn()); // simulate slotRenderEnded const slot = { - getSlotElementId: () => 'ad-unit-1', - getAdUnitPath: () => '/123/foo', - getTargetingKeys: () => ['hb_bidder', 'hb_adid'], - getTargeting: (k) => k === 'hb_bidder' ? ['pubmatic'] : k === 'hb_adid' ? ['ad123'] : [] + getSlotElementId: () => "ad-unit-1", + getAdUnitPath: () => "/123/foo", + getTargetingKeys: () => ["hb_bidder", "hb_adid"], + getTargeting: (k) => + k === "hb_bidder" ? ["pubmatic"] : k === "hb_adid" ? ["ad123"] : [], }; - if (gam._listeners['slotRenderEnded']) { - gam._listeners['slotRenderEnded']({ isEmpty: false, slot }); + if (gam._listeners["slotRenderEnded"]) { + gam._listeners["slotRenderEnded"]({ isEmpty: false, slot }); } expect(server.requests.length).to.be.above(0); }); - it('should NOT send report if a matching bidWon already exists', function () { + it("should NOT send report if a matching bidWon already exists", function () { const gam = createMockGAM(); - withConfigGamPredict(gam); - localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({ gpr: true })); - localStorage.setItem(FIRST_PARTY_KEY, defaultData); + localStorage.setItem( + FIRST_PARTY_KEY + "_" + partner, + JSON.stringify({ gpr: true }) + ); // provide prior bidWon matching placementId and hb_adid events.getEvents.restore(); - sinon.stub(events, 'getEvents').returns([ - { eventType: 'bidWon', args: { adId: 'ad123' }, id: 'ad-unit-1' } - ]); + sinon + .stub(events, "getEvents") + .returns([ + { eventType: "bidWon", args: { adId: "ad123" }, id: "ad-unit-1" }, + ]); events.emit(EVENTS.BID_REQUESTED); - gam.cmd.forEach(fn => fn()); + gam.cmd.forEach((fn) => fn()); const slot = { - getSlotElementId: () => 'ad-unit-1', - getAdUnitPath: () => '/123/foo', - getTargetingKeys: () => ['hb_bidder', 'hb_adid'], - getTargeting: (k) => k === 'hb_bidder' ? ['pubmatic'] : k === 'hb_adid' ? ['ad123'] : [] + getSlotElementId: () => "ad-unit-1", + getAdUnitPath: () => "/123/foo", + getTargetingKeys: () => ["hb_bidder", "hb_adid"], + getTargeting: (k) => + k === "hb_bidder" ? ["pubmatic"] : k === "hb_adid" ? ["ad123"] : [], }; const initialRequests = server.requests.length; - if (gam._listeners['slotRenderEnded']) { - gam._listeners['slotRenderEnded']({ isEmpty: false, slot }); + if (gam._listeners["slotRenderEnded"]) { + gam._listeners["slotRenderEnded"]({ isEmpty: false, slot }); } expect(server.requests.length).to.equal(initialRequests); }); @@ -632,136 +811,213 @@ describe('IntentIQ tests all', function () { const testCasesVrref = [ { - description: 'domainName matches window.top.location.href', + description: "domainName matches window.top.location.href", getWindowSelf: {}, - getWindowTop: { location: { href: 'http://example.com/page' } }, - getWindowLocation: { href: 'http://example.com/page' }, - domainName: 'example.com', - expectedVrref: encodeURIComponent('http://example.com/page'), - shouldContainFui: false + getWindowTop: { location: { href: "http://example.com/page" } }, + getWindowLocation: { href: "http://example.com/page" }, + domainName: "example.com", + expectedVrref: encodeURIComponent("http://example.com/page"), + shouldContainFui: false, }, { - description: 'domainName does not match window.top.location.href', + description: "domainName does not match window.top.location.href", getWindowSelf: {}, - getWindowTop: { location: { href: 'http://anotherdomain.com/page' } }, - getWindowLocation: { href: 'http://anotherdomain.com/page' }, - domainName: 'example.com', - expectedVrref: encodeURIComponent('example.com'), - shouldContainFui: false + getWindowTop: { location: { href: "http://anotherdomain.com/page" } }, + getWindowLocation: { href: "http://anotherdomain.com/page" }, + domainName: "example.com", + expectedVrref: encodeURIComponent("example.com"), + shouldContainFui: false, }, { - description: 'domainName is missing, only fui=1 is returned', + description: "domainName is missing, only fui=1 is returned", getWindowSelf: {}, - getWindowTop: { location: { href: '' } }, - getWindowLocation: { href: '' }, + getWindowTop: { location: { href: "" } }, + getWindowLocation: { href: "" }, domainName: null, - expectedVrref: '', - shouldContainFui: true + expectedVrref: "", + shouldContainFui: true, }, { - description: 'domainName is missing', + description: "domainName is missing", getWindowSelf: {}, - getWindowTop: { location: { href: 'http://example.com/page' } }, - getWindowLocation: { href: 'http://example.com/page' }, + getWindowTop: { location: { href: "http://example.com/page" } }, + getWindowLocation: { href: "http://example.com/page" }, domainName: null, - expectedVrref: encodeURIComponent('http://example.com/page'), - shouldContainFui: false + expectedVrref: encodeURIComponent("http://example.com/page"), + shouldContainFui: false, }, ]; - testCasesVrref.forEach(({ description, getWindowSelf, getWindowTop, getWindowLocation, domainName, expectedVrref, shouldContainFui }) => { - it(`should append correct vrref when ${description}`, function () { - getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(getWindowSelf); - getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns(getWindowTop); - getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns(getWindowLocation); - - const url = 'https://reports.intentiq.com/report?pid=10'; - const modifiedUrl = appendVrrefAndFui(url, domainName); - const urlObj = new URL(modifiedUrl); - - const vrref = encodeURIComponent(urlObj.searchParams.get('vrref') || ''); - const fui = urlObj.searchParams.get('fui'); - - expect(vrref).to.equal(expectedVrref); - expect(urlObj.searchParams.has('fui')).to.equal(shouldContainFui); - if (shouldContainFui) { - expect(fui).to.equal('1'); - } - }); - }); + testCasesVrref.forEach( + ({ + description, + getWindowSelf, + getWindowTop, + getWindowLocation, + domainName, + expectedVrref, + shouldContainFui, + }) => { + it(`should append correct vrref when ${description}`, function () { + getWindowSelfStub = sinon + .stub(utils, "getWindowSelf") + .returns(getWindowSelf); + getWindowTopStub = sinon + .stub(utils, "getWindowTop") + .returns(getWindowTop); + getWindowLocationStub = sinon + .stub(utils, "getWindowLocation") + .returns(getWindowLocation); + + const url = "https://reports.intentiq.com/report?pid=10"; + const modifiedUrl = appendVrrefAndFui(url, domainName); + const urlObj = new URL(modifiedUrl); + + const vrref = encodeURIComponent( + urlObj.searchParams.get("vrref") || "" + ); + const fui = urlObj.searchParams.get("fui"); + + expect(vrref).to.equal(expectedVrref); + expect(urlObj.searchParams.has("fui")).to.equal(shouldContainFui); + if (shouldContainFui) { + expect(fui).to.equal("1"); + } + }); + } + ); const adUnitConfigTests = [ { adUnitConfig: 1, - description: 'should extract adUnitCode first (adUnitConfig = 1)', - event: { adUnitCode: 'adUnitCode-123', placementId: 'placementId-456' }, - expectedPlacementId: 'adUnitCode-123' + description: "should extract adUnitCode first (adUnitConfig = 1)", + event: { adUnitCode: "adUnitCode-123", placementId: "placementId-456" }, + expectedPlacementId: "adUnitCode-123", }, { adUnitConfig: 1, - description: 'should extract placementId if there is no adUnitCode (adUnitConfig = 1)', - event: { placementId: 'placementId-456' }, - expectedPlacementId: 'placementId-456' + description: + "should extract placementId if there is no adUnitCode (adUnitConfig = 1)", + event: { placementId: "placementId-456" }, + expectedPlacementId: "placementId-456", }, { adUnitConfig: 2, - description: 'should extract placementId first (adUnitConfig = 2)', - event: { adUnitCode: 'adUnitCode-123', placementId: 'placementId-456' }, - expectedPlacementId: 'placementId-456' + description: "should extract placementId first (adUnitConfig = 2)", + event: { adUnitCode: "adUnitCode-123", placementId: "placementId-456" }, + expectedPlacementId: "placementId-456", }, { adUnitConfig: 2, - description: 'should extract adUnitCode if there is no placementId (adUnitConfig = 2)', - event: { adUnitCode: 'adUnitCode-123', }, - expectedPlacementId: 'adUnitCode-123' + description: + "should extract adUnitCode if there is no placementId (adUnitConfig = 2)", + event: { adUnitCode: "adUnitCode-123" }, + expectedPlacementId: "adUnitCode-123", }, { adUnitConfig: 3, - description: 'should extract only adUnitCode (adUnitConfig = 3)', - event: { adUnitCode: 'adUnitCode-123', placementId: 'placementId-456' }, - expectedPlacementId: 'adUnitCode-123' + description: "should extract only adUnitCode (adUnitConfig = 3)", + event: { adUnitCode: "adUnitCode-123", placementId: "placementId-456" }, + expectedPlacementId: "adUnitCode-123", }, { adUnitConfig: 4, - description: 'should extract only placementId (adUnitConfig = 4)', - event: { adUnitCode: 'adUnitCode-123', placementId: 'placementId-456' }, - expectedPlacementId: 'placementId-456' + description: "should extract only placementId (adUnitConfig = 4)", + event: { adUnitCode: "adUnitCode-123", placementId: "placementId-456" }, + expectedPlacementId: "placementId-456", }, { adUnitConfig: 1, - description: 'should return empty placementId if neither adUnitCode or placementId exist', + description: + "should return empty placementId if neither adUnitCode or placementId exist", event: {}, - expectedPlacementId: '' + expectedPlacementId: "", }, { adUnitConfig: 1, - description: 'should extract placementId from params array if no top-level adUnitCode or placementId exist (adUnitConfig = 1)', + description: + "should extract placementId from params array if no top-level adUnitCode or placementId exist (adUnitConfig = 1)", event: { - params: [{ someKey: 'value' }, { placementId: 'nested-placementId' }] + params: [{ someKey: "value" }, { placementId: "nested-placementId" }], }, - expectedPlacementId: 'nested-placementId' - } + expectedPlacementId: "nested-placementId", + }, ]; - adUnitConfigTests.forEach(({ adUnitConfig, description, event, expectedPlacementId }) => { - it(description, function () { - const [userConfig] = getUserConfig(); - enableAnalyticWithSpecialOptions({ adUnitConfig }) + adUnitConfigTests.forEach( + ({ adUnitConfig, description, event, expectedPlacementId }) => { + it(description, function () { + enableAnalyticWithSpecialOptions({ adUnitConfig }); + + const testEvent = { ...getWonRequest(), ...event }; + events.emit(EVENTS.BID_WON, testEvent); + + const request = server.requests[0]; + const urlParams = new URL(request.url); + const encodedPayload = urlParams.searchParams.get("payload"); + const decodedPayload = JSON.parse(atob(JSON.parse(encodedPayload)[0])); + + expect(server.requests.length).to.be.above(0); + expect(encodedPayload).to.exist; + expect(decodedPayload).to.have.property( + "placementId", + expectedPlacementId + ); + }); + } + ); - config.getConfig.restore(); - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns([userConfig]); + it("should include ABTestingConfigurationSource in payload when provided", function () { + const ABTestingConfigurationSource = "percentage"; + enableAnalyticWithSpecialOptions({ ABTestingConfigurationSource }); - const testEvent = { ...getWonRequest(), ...event }; - events.emit(EVENTS.BID_WON, testEvent); + events.emit(EVENTS.BID_WON, getWonRequest()); - const request = server.requests[0]; - const urlParams = new URL(request.url); - const encodedPayload = urlParams.searchParams.get('payload'); - const decodedPayload = JSON.parse(atob(JSON.parse(encodedPayload)[0])); + const request = server.requests[0]; + const urlParams = new URL(request.url); + const encodedPayload = urlParams.searchParams.get("payload"); + const decodedPayload = JSON.parse(atob(JSON.parse(encodedPayload)[0])); - expect(server.requests.length).to.be.above(0); - expect(encodedPayload).to.exist; - expect(decodedPayload).to.have.property('placementId', expectedPlacementId); + expect(server.requests.length).to.be.above(0); + expect(decodedPayload).to.have.property( + "ABTestingConfigurationSource", + ABTestingConfigurationSource + ); + }); + + it("should not include ABTestingConfigurationSource in payload when not provided", function () { + enableAnalyticWithSpecialOptions({}); + + events.emit(EVENTS.BID_WON, getWonRequest()); + + const request = server.requests[0]; + const urlParams = new URL(request.url); + const encodedPayload = urlParams.searchParams.get("payload"); + const decodedPayload = JSON.parse(atob(JSON.parse(encodedPayload)[0])); + + expect(server.requests.length).to.be.above(0); + expect(decodedPayload).to.not.have.property("ABTestingConfigurationSource"); + }); + + it("should use group from provided options when ABTestingConfigurationSource is 'group'", function () { + const providedGroup = WITHOUT_IIQ; + // Ensure actualABGroup is not set so group from options is used + delete window[`iiq_identity_${partner}`].actualABGroup; + + enableAnalyticWithSpecialOptions({ + group: providedGroup, + ABTestingConfigurationSource: AB_CONFIG_SOURCE.GROUP, }); + + events.emit(EVENTS.BID_WON, getWonRequest()); + + const request = server.requests[0]; + const urlParams = new URL(request.url); + const encodedPayload = urlParams.searchParams.get("payload"); + const decodedPayload = JSON.parse(atob(JSON.parse(encodedPayload)[0])); + + expect(server.requests.length).to.be.above(0); + // Verify that the group from options is used in the payload + expect(decodedPayload).to.have.property("abGroup", providedGroup); }); }); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 42cff5c2582..18dd0452943 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -6,13 +6,14 @@ import { intentIqIdSubmodule, handleClientHints, firstPartyData as moduleFPD, - isCMPStringTheSame, createPixelUrl, translateMetadata + isCMPStringTheSame, createPixelUrl, translateMetadata, + initializeGlobalIIQ } from '../../../modules/intentIqIdSystem.js'; import { storage, readData, storeData } from '../../../libraries/intentIqUtils/storageUtils.js'; import { gppDataHandler, uspDataHandler, gdprDataHandler } from '../../../src/consentHandler.js'; import { clearAllCookies } from '../../helpers/cookies.js'; import { detectBrowser, detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils.js'; -import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, NOT_YET_DEFINED, PREBID, WITH_IIQ, WITHOUT_IIQ} from '../../../libraries/intentIqConstants/intentIqConstants.js'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, PREBID, WITH_IIQ, WITHOUT_IIQ} from '../../../libraries/intentIqConstants/intentIqConstants.js'; import { decryptData } from '../../../libraries/intentIqUtils/cryptionUtils.js'; import { isCHSupported } from '../../../libraries/intentIqUtils/chUtils.js'; @@ -81,34 +82,25 @@ function ensureUAData() { } async function waitForClientHints() { - if (!isCHSupported()) return; - const clock = globalThis.__iiqClock; if (clock && typeof clock.runAllAsync === 'function') { await clock.runAllAsync(); - return; - } - if (clock && typeof clock.runAll === 'function') { + } else if (clock && typeof clock.runAll === 'function') { clock.runAll(); await Promise.resolve(); await Promise.resolve(); - return; - } - if (clock && typeof clock.runToLast === 'function') { + } else if (clock && typeof clock.runToLast === 'function') { clock.runToLast(); await Promise.resolve(); - return; - } - if (clock && typeof clock.tick === 'function') { + } else if (clock && typeof clock.tick === 'function') { clock.tick(0); await Promise.resolve(); - return; + } else { + await Promise.resolve(); + await Promise.resolve(); + await new Promise(r => setTimeout(r, 0)); } - - await Promise.resolve(); - await Promise.resolve(); - await new Promise(r => setTimeout(r, 0)); } const testAPILink = 'https://new-test-api.intentiq.com' @@ -131,7 +123,22 @@ const mockGAM = () => { }; }; +const regionCases = [ + { name: 'no region (default)', region: undefined, expected: 'https://api.intentiq.com' }, + { name: 'apac', region: 'apac', expected: 'https://api-apac.intentiq.com' }, + { name: 'emea', region: 'emea', expected: 'https://api-emea.intentiq.com' }, + { name: 'gdpr', region: 'gdpr', expected: 'https://api-gdpr.intentiq.com' } +]; + +const syncRegionCases = [ + { name: 'default', region: undefined, expected: 'https://sync.intentiq.com' }, + { name: 'apac', region: 'apac', expected: 'https://sync-apac.intentiq.com' }, + { name: 'emea', region: 'emea', expected: 'https://sync-emea.intentiq.com' }, + { name: 'gdpr', region: 'gdpr', expected: 'https://sync-gdpr.intentiq.com' }, +]; + describe('IntentIQ tests', function () { + this.timeout(10000); let sandbox; let logErrorStub; let clock; @@ -178,6 +185,23 @@ describe('IntentIQ tests', function () { localStorage.clear(); }); + it('should create global IIQ identity object', async () => { + const globalName = `iiq_identity_${partner}` + const callBackSpy = sinon.spy(); + const submoduleCallback = intentIqIdSubmodule.getId({ params: { partner }}).callback; + submoduleCallback(callBackSpy); + await waitForClientHints() + expect(window[globalName]).to.be.not.undefined + expect(window[globalName].partnerData).to.be.not.undefined + expect(window[globalName].firstPartyData).to.be.not.undefined + }) + + it('should not create a global IIQ identity object in case it was already created', () => { + intentIqIdSubmodule.getId({ params: { partner }}) + const secondTimeCalling = initializeGlobalIIQ(partner) + expect(secondTimeCalling).to.be.false + }) + it('should log an error if no configParams were passed when getId', function () { const submodule = intentIqIdSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; @@ -330,10 +354,11 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should set GAM targeting to U initially and update to A after server response', async function () { + it('should set GAM targeting to B initially and update to A after server response', async function () { const callBackSpy = sinon.spy(); const mockGamObject = mockGAM(); const expectedGamParameterName = 'intent_iq_group'; + defaultConfigParams.params.abPercentage = 0; // "B" provided percentage by user const originalPubads = mockGamObject.pubads; const setTargetingSpy = sinon.spy(); @@ -350,33 +375,69 @@ describe('IntentIQ tests', function () { defaultConfigParams.params.gamObjectReference = mockGamObject; const submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; - submoduleCallback(callBackSpy); await waitForClientHints(); const request = server.requests[0]; mockGamObject.cmd.forEach(cb => cb()); - mockGamObject.cmd = [] + mockGamObject.cmd = []; const groupBeforeResponse = mockGamObject.pubads().getTargeting(expectedGamParameterName); - request.respond( - 200, - responseHeader, - JSON.stringify({ group: 'A', tc: 20 }) - ); + request.respond(200, responseHeader, JSON.stringify({ tc: 20 })); - mockGamObject.cmd.forEach(item => item()); + mockGamObject.cmd.forEach(cb => cb()); + mockGamObject.cmd = []; const groupAfterResponse = mockGamObject.pubads().getTargeting(expectedGamParameterName); expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39'); - expect(groupBeforeResponse).to.deep.equal([NOT_YET_DEFINED]); + expect(groupBeforeResponse).to.deep.equal([WITHOUT_IIQ]); expect(groupAfterResponse).to.deep.equal([WITH_IIQ]); - expect(setTargetingSpy.calledTwice).to.be.true; }); + it('should set GAM targeting to B when server tc=41', async () => { + window.localStorage.clear(); + const mockGam = mockGAM(); + defaultConfigParams.params.gamObjectReference = mockGam; + defaultConfigParams.params.abPercentage = 100; + + const cb = intentIqIdSubmodule.getId(defaultConfigParams).callback; + cb(() => {}); + await waitForClientHints(); + + const req = server.requests[0]; + mockGam.cmd.forEach(fn => fn()); + const before = mockGam.pubads().getTargeting('intent_iq_group'); + + req.respond(200, responseHeader, JSON.stringify({ tc: 41 })); + mockGam.cmd.forEach(fn => fn()); + const after = mockGam.pubads().getTargeting('intent_iq_group'); + + expect(before).to.deep.equal([WITH_IIQ]); + expect(after).to.deep.equal([WITHOUT_IIQ]); + }); + + it('should read tc from LS and set relevant GAM group', async () => { + window.localStorage.clear(); + const storageKey = `${FIRST_PARTY_KEY}_${defaultConfigParams.params.partner}`; + localStorage.setItem(storageKey, JSON.stringify({ terminationCause: 41 })); + + const mockGam = mockGAM(); + defaultConfigParams.params.gamObjectReference = mockGam; + defaultConfigParams.params.abPercentage = 100; + + const cb = intentIqIdSubmodule.getId(defaultConfigParams).callback; + cb(() => {}); + await waitForClientHints(); + + mockGam.cmd.forEach(fn => fn()); + const group = mockGam.pubads().getTargeting('intent_iq_group'); + + expect(group).to.deep.equal([WITHOUT_IIQ]); + }); + it('should use the provided gamParameterName from configParams', function () { const callBackSpy = sinon.spy(); const mockGamObject = mockGAM(); @@ -409,7 +470,6 @@ describe('IntentIQ tests', function () { localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ pcid: 'pcid-1', pcidDate: Date.now(), - group: 'A', isOptedOut: false, date: Date.now(), sCal: Date.now() @@ -551,7 +611,7 @@ describe('IntentIQ tests', function () { it('should send AT=20 request and send spd in it', async function () { const spdValue = { foo: 'bar', value: 42 }; const encodedSpd = encodeURIComponent(JSON.stringify(spdValue)); - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({pcid: '123', spd: spdValue})); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({pcid: '123', spd: spdValue})); intentIqIdSubmodule.getId({params: { partner: 10, @@ -569,7 +629,7 @@ describe('IntentIQ tests', function () { it('should send AT=20 request and send spd string in it ', async function () { const spdValue = 'server provided data'; const encodedSpd = encodeURIComponent(spdValue); - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({pcid: '123', spd: spdValue})); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({pcid: '123', spd: spdValue})); intentIqIdSubmodule.getId({params: { partner: 10, @@ -588,7 +648,7 @@ describe('IntentIQ tests', function () { const spdValue = { foo: 'bar', value: 42 }; const encodedSpd = encodeURIComponent(JSON.stringify(spdValue)); - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ pcid: '123', spd: spdValue })); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({ pcid: '123', spd: spdValue })); const callBackSpy = sinon.spy(); const submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; @@ -604,7 +664,7 @@ describe('IntentIQ tests', function () { it('should send spd string from firstPartyData in localStorage in at=39 request', async function () { const spdValue = 'spd string'; const encodedSpd = encodeURIComponent(spdValue); - localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify({ pcid: '123', spd: spdValue })); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, JSON.stringify({ pcid: '123', spd: spdValue })); const callBackSpy = sinon.spy(); const submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; @@ -630,7 +690,7 @@ describe('IntentIQ tests', function () { JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true, spd: spdValue }) ); - const storedLs = readData(FIRST_PARTY_KEY, ['html5', 'cookie'], storage); + const storedLs = readData(FIRST_PARTY_KEY + '_' + partner, ['html5', 'cookie'], storage); const parsedLs = JSON.parse(storedLs); expect(storedLs).to.not.be.null; @@ -692,13 +752,12 @@ describe('IntentIQ tests', function () { expect(result).to.equal('unknown'); }); - it("Should call the server for new partner if FPD has been updated by other partner, and 24 hours have not yet passed.", async () => { + it("Should call the server for new partner if FPD has been updated by other partner, and 72 hours have not yet passed.", async () => { const allowedStorage = ['html5'] const newPartnerId = 12345 const FPD = { pcid: 'c869aa1f-fe40-47cb-810f-4381fec28fc9', pcidDate: 1747720820757, - group: 'A', sCal: Date.now(), gdprString: null, gppString: null, @@ -713,13 +772,13 @@ describe('IntentIQ tests', function () { const request = server.requests[0]; expect(request.url).contain("ProfilesEngineServlet?at=39") // server was called }) - it("Should NOT call the server if FPD has been updated user Opted Out, and 24 hours have not yet passed.", async () => { + + it("Should NOT call the server if FPD has been updated user Opted Out, and 72 hours have not yet passed.", async () => { const allowedStorage = ['html5'] const newPartnerId = 12345 const FPD = { pcid: 'c869aa1f-fe40-47cb-810f-4381fec28fc9', pcidDate: 1747720820757, - group: 'A', isOptedOut: true, sCal: Date.now(), gdprString: null, @@ -861,23 +920,15 @@ describe('IntentIQ tests', function () { expect(callbackArgument).to.deep.equal({ eids: [] }); // Ensure that runtimeEids was updated to { eids: [] } }); - it('should make request to correct address api-gdpr.intentiq.com if gdpr is detected', async function() { - const ENDPOINT_GDPR = 'https://api-gdpr.intentiq.com'; - mockConsentHandlers(uspData, gppData, gdprData); - const callBackSpy = sinon.spy(); - const submoduleCallback = intentIqIdSubmodule.getId({...defaultConfigParams}).callback; - - submoduleCallback(callBackSpy); - await waitForClientHints(); - const request = server.requests[0]; - - expect(request.url).to.contain(ENDPOINT_GDPR); - }); - it('should make request to correct address with iiqServerAddress parameter', async function() { - defaultConfigParams.params.iiqServerAddress = testAPILink + const customParams = { + params: { + ...defaultConfigParams.params, + iiqServerAddress: testAPILink + } + }; const callBackSpy = sinon.spy(); - const submoduleCallback = intentIqIdSubmodule.getId({...defaultConfigParams}).callback; + const submoduleCallback = intentIqIdSubmodule.getId({...customParams}).callback; submoduleCallback(callBackSpy); await waitForClientHints(); @@ -904,6 +955,57 @@ describe('IntentIQ tests', function () { const request = server.requests[0]; expect(request.url).to.contain(syncTestAPILink); }); + + regionCases.forEach(({ name, region, expected }) => { + it(`should use region-specific api endpoint when region is "${name}"`, async function () { + mockConsentHandlers(uspData, gppData, gdprData); // gdprApplies = true + + const callBackSpy = sinon.spy(); + const configWithRegion = { + params: { + ...defaultConfigParams.params, + region + } + }; + + const submoduleCallback = intentIqIdSubmodule.getId(configWithRegion).callback; + submoduleCallback(callBackSpy); + await waitForClientHints(); + + const request = server.requests[0]; + expect(request.url).to.contain(expected); + }); + }); + + syncRegionCases.forEach(({ name, region, expected }) => { + it(`should use region-specific sync endpoint when region is "${name}"`, async function () { + let wasCallbackCalled = false; + + const callbackConfigParams = { + params: { + partner, + pai, + partnerClientIdType, + partnerClientId, + browserBlackList: 'Chrome', + region, + callback: () => { + wasCallbackCalled = true; + } + } + }; + + mockConsentHandlers(uspData, gppData, gdprData); + + intentIqIdSubmodule.getId(callbackConfigParams); + + await waitForClientHints(); + + const request = server.requests[0]; + expect(request.url).to.contain(expected); + expect(wasCallbackCalled).to.equal(true); + }); + }); }); it('should get and save client hints to storage', async () => { @@ -1547,4 +1649,35 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; expect(groupChangedSpy.calledWith(WITH_IIQ)).to.be.true; }); + + it('should use group provided by partner', async function () { + const groupChangedSpy = sinon.spy(); + const callBackSpy = sinon.spy(); + const usedGroup = 'B' + const ABTestingConfigurationSource = 'group' + const configParams = { + params: { + ...defaultConfigParams.params, + ABTestingConfigurationSource, + group: usedGroup, + groupChanged: groupChangedSpy + } + }; + + const submoduleCallback = intentIqIdSubmodule.getId(configParams).callback; + submoduleCallback(callBackSpy); + await waitForClientHints() + const request = server.requests[0]; + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) + ); + + expect(request.url).to.contain(`abtg=${usedGroup}`); + expect(request.url).to.contain(`ABTestingConfigurationSource=${ABTestingConfigurationSource}`); + expect(request.url).to.contain(`testGroup=${usedGroup}`); + expect(callBackSpy.calledOnce).to.be.true; + expect(groupChangedSpy.calledWith(usedGroup)).to.be.true; + }); }); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 2e04551a4a1..c3c99788a70 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -444,7 +444,10 @@ describe('invibesBidAdapter:', function () { it('has capped ids if local storage variable is correctly formatted', function () { top.window.invibes.optIn = 1; top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; - localStorage.ivvcap = '{"9731":[1,1768600800000]}'; + const now = new Date().getTime(); + localStorage.ivvcap = JSON.stringify({ + 9731: [1, now + (24 * 60 * 60 * 1000)] + }) SetBidderAccess(); const request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index c14b85b2c8b..7f48b7077bf 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -480,7 +480,7 @@ describe('IQZoneBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -489,9 +489,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -500,7 +498,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index d7f9a233c9d..d7cbd189782 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('KiviAdsBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -492,9 +492,7 @@ describe('KiviAdsBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -503,7 +501,7 @@ describe('KiviAdsBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 4e70ac6f9f3..8b771e3eef7 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -26,9 +26,9 @@ function createBidderRequest(auctionId, timeout, pageUrl, gdprVendorData = {}, p }; } -function createValidBidRequest(params, bidId, sizes) { +function createValidBidRequest(params, bidId, sizes, adUnitCode) { const validBidRequest = { - adUnitCode: 'adunit-code', + adUnitCode: adUnitCode || 'adunit-code', bidId: bidId || '22c4871113f461', bidder: 'kobler', bidderRequestId: '15246a574e859f', @@ -451,7 +451,8 @@ describe('KoblerAdapter', function () { dealIds: ['623472534328234'] }, '953ee65d-d18a-484f-a840-d3056185a060', - [[400, 600]] + [[400, 600]], + 'ad-unit-1' ), createValidBidRequest( { @@ -459,12 +460,14 @@ describe('KoblerAdapter', function () { dealIds: ['92368234753283', '263845832942'] }, '8320bf79-9d90-4a17-87c6-5d505706a921', - [[400, 500], [200, 250], [300, 350]] + [[400, 500], [200, 250], [300, 350]], + 'ad-unit-2' ), createValidBidRequest( undefined, 'd0de713b-32e3-4191-a2df-a007f08ffe72', - [[800, 900]] + [[800, 900]], + 'ad-unit-3' ) ]; const bidderRequest = createBidderRequest( @@ -520,6 +523,11 @@ describe('KoblerAdapter', function () { id: '623472534328234' } ] + }, + ext: { + prebid: { + adunitcode: 'ad-unit-1' + } } }, { @@ -553,6 +561,11 @@ describe('KoblerAdapter', function () { id: '263845832942' } ] + }, + ext: { + prebid: { + adunitcode: 'ad-unit-2' + } } }, { @@ -569,7 +582,12 @@ describe('KoblerAdapter', function () { }, bidfloor: 0, bidfloorcur: 'USD', - pmp: {} + pmp: {}, + ext: { + prebid: { + adunitcode: 'ad-unit-3' + } + } } ], device: { diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 98bdbcbb855..044d9d4f26b 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('KrushmediabBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -492,9 +492,7 @@ describe('KrushmediabBidAdapter', function () { 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: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -503,7 +501,7 @@ describe('KrushmediabBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/leagueMBidAdapter_spec.js b/test/spec/modules/leagueMBidAdapter_spec.js new file mode 100644 index 00000000000..4c0f9a53529 --- /dev/null +++ b/test/spec/modules/leagueMBidAdapter_spec.js @@ -0,0 +1,441 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/leagueMBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; + +const ENDPOINT = 'https://pbjs.league-m.media'; + +const defaultRequest = { + tmax: 0, + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'leagueM', + params: { + pid: 'aa8217e20131c095fe9dba67981040b0', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skippable: true + } +}; + +const videoBidderRequest = { + bidderCode: 'leagueM', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'leagueM', + bids: [{bidId: 'qwerty'}] +}; + +describe('leagueMBidAdapter', () => { + 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 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('tmax').and.to.equal(defaultRequest.tmax); + 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('gdprConsent').and.to.deep.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({ + pid: 'aa8217e20131c095fe9dba67981040b0' + }); + 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); + const bidderRequest = { + ortb2: { + source: { + ext: { + schain: { + ver: '1.0' + } + } + } + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], bidderRequest).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + 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 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', + skippable: 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: ['leagueM'] + }, + 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: ['leagueM']}); + }); + + 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: '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('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: '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('demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('should 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/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index a4b161b7026..a4cff04599b 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/limelightDigitalBidAdapter.js'; +import { deepAccess } from '../../../src/utils.js'; describe('limelightDigitalAdapter', function () { const bid1 = { @@ -42,25 +43,9 @@ describe('limelightDigitalAdapter', function () { } ] } - ], - ortb2: { - source: { - ext: { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '1', - hp: 1 - } - ] - } - } - } - } - } + ] + }; + const bid2 = { bidId: '58ee9870c3164a', bidder: 'limelightDigital', @@ -77,13 +62,17 @@ describe('limelightDigitalAdapter', function () { }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', - sizes: [[350, 200]], + mediaTypes: { + banner: { + sizes: [[350, 200]] + } + }, ortb2Imp: { ext: { - gpid: '/1111/homepage#300x250', + gpid: '/1111/homepage#350x200', tid: '738d5915-6651-43b9-9b6b-d50517350917', data: { - 'pbadslot': '/1111/homepage#300x250' + 'pbadslot': '/1111/homepage#350x200' } } }, @@ -96,30 +85,9 @@ describe('limelightDigitalAdapter', function () { } ] } - ], - ortb2: { - source: { - ext: { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '1', - hp: 1 - }, - { - asi: 'example1.com', - sid: '2', - hp: 1 - } - ] - } - } - } - } - } + ] + }; + const bid3 = { bidId: '019645c7d69460', bidder: 'limelightDigital', @@ -137,103 +105,77 @@ describe('limelightDigitalAdapter', function () { }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', - sizes: [[800, 600]], - ortb2Imp: { - ext: { - gpid: '/1111/homepage#300x250', - tid: '738d5915-6651-43b9-9b6b-d50517350917', - data: { - 'pbadslot': '/1111/homepage#300x250' - } - } - }, - userIdAsEids: [ - { - source: 'test3.org', - uids: [ - { - id: '345', - }, - { - id: '456', - } - ] - } - ], - ortb2: { - source: { - ext: { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '1', - hp: 1 - } - ] - } - } + mediaTypes: { + video: { + context: 'instream', + playerSize: [[800, 600]], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 3, 5, 6], + maxduration: 60, + minduration: 3, + api: [2], + playbackmethod: [1] } - } - } - const bid4 = { - bidId: '019645c7d69460', - bidder: 'limelightDigital', - bidderRequestId: 'f2b15f89e77ba6', - params: { - host: 'exchange.ortb.net', - adUnitId: 789, - adUnitType: 'video', - custom1: 'custom1', - custom2: 'custom2', - custom3: 'custom3', - custom4: 'custom4', - custom5: 'custom5' - }, - placementCode: 'placement_2', - auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', - video: { - playerSize: [800, 600] }, ortb2Imp: { ext: { - gpid: '/1111/homepage#300x250', + gpid: '/1111/homepage#800x600', tid: '738d5915-6651-43b9-9b6b-d50517350917', data: { - 'pbadslot': '/1111/homepage#300x250' + 'pbadslot': '/1111/homepage#800x600' } } }, userIdAsEids: [ { - source: 'test.org', + source: 'test3.org', uids: [ { - id: '111', + id: '345', } ] } - ], - ortb2: { - source: { - ext: { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '1', - hp: 1 - } - ] - } - } - } - } - } + ] + }; + + describe('isBidRequestValid', function() { + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid1)).to.equal(true); + expect(spec.isBidRequestValid(bid2)).to.equal(true); + expect(spec.isBidRequestValid(bid3)).to.equal(true); + }); + + it('should return true when adUnitId is zero', function() { + const bidWithZeroId = { ...bid1, params: { ...bid1.params, adUnitId: 0 } }; + expect(spec.isBidRequestValid(bidWithZeroId)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + const bidFailed = { + bidder: 'limelightDigital', + bidderRequestId: '145e1d6a7837c9', + params: { + adUnitId: 123, + adUnitType: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2' + }; + expect(spec.isBidRequestValid(bidFailed)).to.equal(false); + }); + + it('should return false when host is missing', function() { + const bidWithoutHost = { ...bid1, params: { ...bid1.params } }; + delete bidWithoutHost.params.host; + expect(spec.isBidRequestValid(bidWithoutHost)).to.equal(false); + }); + + it('should return false when adUnitType is missing', function() { + const bidWithoutType = { ...bid1, params: { ...bid1.params } }; + delete bidWithoutType.params.adUnitType; + expect(spec.isBidRequestValid(bidWithoutType)).to.equal(false); + }); + }); describe('buildRequests', function () { const bidderRequest = { @@ -245,524 +187,636 @@ describe('limelightDigitalAdapter', function () { mobile: 1, architecture: 'arm' } + }, + site: { + page: 'https://example.com/page' } }, refererInfo: { - page: 'testPage' + page: 'https://example.com/page' } - } - const serverRequests = spec.buildRequests([bid1, bid2, bid3, bid4], bidderRequest) - it('Creates two ServerRequests', function() { - expect(serverRequests).to.exist - expect(serverRequests).to.have.lengthOf(2) - }) - serverRequests.forEach(serverRequest => { - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist - expect(serverRequest.method).to.exist - expect(serverRequest.url).to.exist - expect(serverRequest.data).to.exist - }) - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST') - }) - it('Returns valid data if array of bids is valid', function () { - const data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys( - 'deviceWidth', - 'deviceHeight', - 'secure', - 'adUnits', - 'sua', - 'page', - 'ortb2', - 'refererInfo' - ); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.secure).to.be.a('boolean'); - data.adUnits.forEach(adUnit => { - expect(adUnit).to.have.all.keys( - 'id', - 'bidId', - 'type', - 'sizes', - 'transactionId', - 'publisherId', - 'userIdAsEids', - 'supplyChain', - 'custom1', - 'custom2', - 'custom3', - 'custom4', - 'custom5', - 'ortb2Imp' - ); - expect(adUnit.id).to.be.a('number'); - expect(adUnit.bidId).to.be.a('string'); - expect(adUnit.type).to.be.a('string'); - expect(adUnit.transactionId).to.be.a('string'); - expect(adUnit.sizes).to.be.an('array'); - expect(adUnit.userIdAsEids).to.be.an('array'); - expect(adUnit.supplyChain).to.be.an('object'); - expect(adUnit.custom1).to.be.a('string'); - expect(adUnit.custom2).to.be.a('string'); - 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'); - expect(data.sua.mobile).to.be.a('number'); - 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 () { - expect(serverRequests[0].url).to.equal('https://exchange.ortb.net/hb') - expect(serverRequests[1].url).to.equal('https://ads.project-limelight.com/hb') - }) - it('Returns valid adUnits', function () { - validateAdUnit(serverRequests[0].data.adUnits[0], bid1) - validateAdUnit(serverRequests[1].data.adUnits[0], bid2) - validateAdUnit(serverRequests[0].data.adUnits[1], bid3) - }) - it('Returns empty data if no valid requests are passed', function () { - const serverRequests = spec.buildRequests([]) - expect(serverRequests).to.be.an('array').that.is.empty - }) - it('Returns request with page field value from ortb2 object if ortb2 has page field', function () { - bidderRequest.ortb2.site = { - page: 'testSitePage' - } - const serverRequests = spec.buildRequests([bid1], bidderRequest) - expect(serverRequests).to.have.lengthOf(1) - serverRequests.forEach(serverRequest => { - expect(serverRequest.data.page).to.be.a('string'); - expect(serverRequest.data.page).to.be.equal('testSitePage'); - }) - }) - }) - describe('interpretBannerResponse', function () { - const resObject = { - body: [ { - requestId: '123', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['example.com'], - mediaType: 'banner' - } - } ] }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - const dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'meta'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.meta.advertiserDomains).to.be.an('array'); - expect(dataItem.meta.mediaType).to.be.a('string'); + + it('should create two server requests for different hosts', function() { + const serverRequests = spec.buildRequests([bid1, bid2, bid3], bidderRequest); + expect(serverRequests).to.exist; + expect(serverRequests).to.have.lengthOf(2); + }); + + it('should create ServerRequest objects with method, URL and data', function () { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + serverRequests.forEach(serverRequest => { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + }); + + it('should return POST method', function () { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + serverRequests.forEach(serverRequest => { + expect(serverRequest.method).to.equal('POST'); + }); + }); + + it('should return valid OpenRTB request structure', function () { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const data = serverRequests[0].data; + + expect(data).to.be.an('object'); + expect(data).to.have.property('imp'); + expect(data).to.have.property('site'); + expect(data).to.have.property('device'); + expect(data).to.have.property('id'); + expect(data.imp).to.be.an('array'); + }); + + it('should include custom fields in imp.ext', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const imp = serverRequests[0].data.imp[0]; + + expect(deepAccess(imp, 'ext.c1')).to.equal('custom1'); + expect(deepAccess(imp, 'ext.c2')).to.equal('custom2'); + expect(deepAccess(imp, 'ext.c3')).to.equal('custom3'); + expect(deepAccess(imp, 'ext.c4')).to.equal('custom4'); + expect(deepAccess(imp, 'ext.c5')).to.equal('custom5'); + }); + + it('should include adUnitId in imp.ext', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const imp = serverRequests[0].data.imp[0]; + + expect(deepAccess(imp, 'ext.adUnitId')).to.equal(123); + }); + + it('should return valid URLs for different hosts', function () { + const serverRequests = spec.buildRequests([bid1, bid2, bid3], bidderRequest); + + const exchangeRequest = serverRequests.find(req => req.url.includes('exchange.ortb.net')); + const adsRequest = serverRequests.find(req => req.url.includes('ads.project-limelight.com')); + + expect(exchangeRequest.url).to.equal('https://exchange.ortb.net/ortbhb'); + expect(adsRequest.url).to.equal('https://ads.project-limelight.com/ortbhb'); + }); + + it('should group bids by host correctly', function() { + const serverRequests = spec.buildRequests([bid1, bid2, bid3], bidderRequest); + + const exchangeRequest = serverRequests.find(req => req.url.includes('exchange.ortb.net')); + const adsRequest = serverRequests.find(req => req.url.includes('ads.project-limelight.com')); + + expect(exchangeRequest.data.imp).to.have.lengthOf(2); + expect(adsRequest.data.imp).to.have.lengthOf(1); + }); + + it('should return empty array if no valid requests are passed', function () { + const serverRequests = spec.buildRequests([], bidderRequest); + expect(serverRequests).to.be.an('array').that.is.empty; + }); + + it('should include banner format in OpenRTB request', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const imp = serverRequests[0].data.imp[0]; + + expect(imp.banner).to.exist; + expect(imp.banner.format).to.be.an('array'); + expect(imp.banner.format[0]).to.have.property('w', 300); + expect(imp.banner.format[0]).to.have.property('h', 250); + }); + + it('should include video object in OpenRTB request for video bid', function() { + const serverRequests = spec.buildRequests([bid3], bidderRequest); + const imp = serverRequests[0].data.imp[0]; + if (FEATURES.VIDEO) { + expect(imp.video).to.exist; + expect(imp.video).to.be.an('object'); + expect(imp.video.w).to.equal(800); + expect(imp.video.h).to.equal(600); } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; + expect(deepAccess(imp, 'ext.adUnitId')).to.equal(789); + }); + + it('should skip custom fields if they are undefined', function() { + const bidWithoutCustom = { ...bid1, params: { ...bid1.params } }; + delete bidWithoutCustom.params.custom1; + delete bidWithoutCustom.params.custom2; + + const serverRequests = spec.buildRequests([bidWithoutCustom], bidderRequest); + const imp = serverRequests[0].data.imp[0]; + + expect(deepAccess(imp, 'ext.c1')).to.be.undefined; + expect(deepAccess(imp, 'ext.c2')).to.be.undefined; + expect(deepAccess(imp, 'ext.c3')).to.equal('custom3'); + }); + + it('should handle various refererInfo scenarios', function () { + const baseRequest = [{ + bidder: 'limelightDigital', + params: { host: 'exchange.example.com', adUnitId: 'test' }, + mediaTypes: { banner: { sizes: [[300, 250]] } }, + bidId: 'test-bid-id' + }]; + + let requests = spec.buildRequests(baseRequest, { + refererInfo: { page: 'https://test.com' }, + ortb2: {} }); + expect(requests[0].data.site.page).to.equal('https://test.com'); + + requests = spec.buildRequests(baseRequest, { + refererInfo: { page: 'https://referer.com' }, + ortb2: { site: { page: 'https://ortb2.com' } } + }); + expect(requests[0].data.site.page).to.equal('https://ortb2.com'); + + requests = spec.buildRequests(baseRequest, { ortb2: {} }); + expect(requests[0].data.site.page).to.be.undefined; }); - }); - describe('interpretVideoResponse', function () { - const resObject = { - body: [ { - requestId: '123', - cpm: 0.3, - width: 320, - height: 50, - vastXml: '', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['example.com'], - mediaType: 'video' + + describe('buildRequests - size handling', function () { + it('should handle mediaTypes.banner.sizes', function () { + const bidRequests = [{ + bidder: 'limelightDigital', + params: { + host: 'exchange.example.com', + adUnitId: 'test', + adUnitType: 'banner' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + adUnitCode: 'test-ad-unit', + bidId: 'test-bid-id' + }]; + + const bidderRequest = { + refererInfo: { page: 'https://test.com' }, + ortb2: { site: { domain: 'test.com' } } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + + expect(requests[0].data.imp[0].banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 728, h: 90 } + ]); + }); + + it('should handle legacy sizes without mediaTypes', function () { + const bidRequests = [{ + bidder: 'limelightDigital', + params: { + host: 'exchange.example.com', + adUnitId: 'test', + adUnitType: 'banner' + }, + sizes: [[300, 250], [728, 90]], + adUnitCode: 'test-ad-unit', + bidId: 'test-bid-id' + }]; + + const bidderRequest = { + refererInfo: { page: 'https://test.com' }, + ortb2: { site: { domain: 'test.com' } } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + + expect(requests[0].data.imp[0].banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 728, h: 90 } + ]); + }); + + it('should merge mediaTypes sizes with bidRequest.sizes', function () { + const bidRequests = [{ + bidder: 'limelightDigital', + params: { + host: 'exchange.example.com', + adUnitId: 'test', + adUnitType: 'banner' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + sizes: [[728, 90]], + adUnitCode: 'test-ad-unit', + bidId: 'test-bid-id' + }]; + + const bidderRequest = { + refererInfo: { page: 'https://test.com' }, + ortb2: { site: { domain: 'test.com' } } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + + const formats = requests[0].data.imp[0].banner.format; + expect(formats).to.have.lengthOf(2); + expect(formats).to.deep.include({ w: 300, h: 250 }); + expect(formats).to.deep.include({ w: 728, h: 90 }); + }); + + it('should handle video with playerSize', function () { + const bidRequests = [{ + bidder: 'limelightDigital', + params: { + host: 'exchange.example.com', + adUnitId: 'test', + adUnitType: 'video' + }, + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + adUnitCode: 'test-ad-unit', + bidId: 'test-bid-id' + }]; + + const bidderRequest = { + refererInfo: { page: 'https://test.com' }, + ortb2: { site: { domain: 'test.com' } } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + if (FEATURES.VIDEO) { + expect(requests[0].data.imp[0].video).to.exist; + expect(requests[0].data.imp[0].video.w).to.equal(640); + expect(requests[0].data.imp[0].video.h).to.equal(480); } - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - const dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'meta'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.vastXml).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.meta.advertiserDomains).to.be.an('array'); - expect(dataItem.meta.mediaType).to.be.a('string'); - } - it('should return an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; }); }); }); - describe('isBidRequestValid', function() { - const bid = { - bidId: '2dd581a2b6281d', - bidder: 'limelightDigital', - bidderRequestId: '145e1d6a7837c9', - params: { - host: 'exchange.ortb.net', - adUnitId: 123, - adUnitType: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + + describe('interpretResponse - Banner', function () { + const bidderRequest = { + ortb2: { + site: { + page: 'https://example.com/page' + } + } }; - it('should return true when required params found', function() { - [bid, bid1, bid2, bid3].forEach(bid => { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); + it('should return array of valid bid responses', function () { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const request = serverRequests[0]; + + const ortbResponse = { + body: { + seatbid: [{ + bid: [{ + id: 'bid123', + impid: request.data.imp[0].id, + price: 0.3, + w: 300, + h: 250, + adm: '

Hello ad

', + crid: '123asd', + mtype: 1, + adomain: ['example.com'], + exp: 1000 + }] + }], + cur: 'USD' + } + }; + + const serverResponses = spec.interpretResponse(ortbResponse, request); + + expect(serverResponses).to.be.an('array').that.is.not.empty; + expect(serverResponses).to.have.lengthOf(1); + + const bidResponse = serverResponses[0]; + expect(bidResponse.requestId).to.be.a('string'); + expect(bidResponse.cpm).to.equal(0.3); + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + expect(bidResponse.ad).to.be.a('string'); + expect(bidResponse.ttl).to.be.a('number'); + expect(bidResponse.creativeId).to.be.a('string'); + expect(bidResponse.netRevenue).to.be.a('boolean'); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.mediaType).to.equal('banner'); }); - it('should return true when adUnitId is zero', function() { - bid.params.adUnitId = 0; - expect(spec.isBidRequestValid(bid)).to.equal(true); + it('should return empty array for invalid response', function () { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const request = serverRequests[0]; + + const serverResponses = spec.interpretResponse({ body: null }, request); + expect(serverResponses).to.be.an('array').that.is.empty; }); - it('should return false when required params are not passed', function() { - const bidFailed = { - bidder: 'limelightDigital', - bidderRequestId: '145e1d6a7837c9', - params: { - adUnitId: 123, - adUnitType: 'banner' - }, - placementCode: 'placement_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + it('should return empty array when response body is missing', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const request = serverRequests[0]; + + const serverResponses = spec.interpretResponse({}, request); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + + it('should filter out invalid bids', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const request = serverRequests[0]; + + const invalidResponse = { + body: { + seatbid: [{ + bid: [{ + id: 'bid123', + impid: request.data.imp[0].id, + price: 0.3, + crid: '123asd', + mtype: 1, + adomain: ['example.com'] + }] + }], + cur: 'USD' + } }; - expect(spec.isBidRequestValid(bidFailed)).to.equal(false); + + const serverResponses = spec.interpretResponse(invalidResponse, request); + expect(serverResponses).to.be.an('array').that.is.empty; }); }); - describe('interpretResponse', function() { - const resObject = { - requestId: '123', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['example.com'], - mediaType: 'banner' + + describe('interpretResponse - Video', function () { + const bidderRequest = { + ortb2: { + site: { + page: 'https://example.com/page' + } } }; - it('should skip responses which do not contain required params', function() { - const bidResponses = { - body: [ { - cpm: 0.3, - ttl: 1000, - currency: 'USD', - meta: { - advertiserDomains: ['example.com'], - mediaType: 'banner' - } - }, resObject ] - } - expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); - }); - it('should skip responses which do not contain advertiser domains', function() { - const resObjectWithoutAdvertiserDomains = Object.assign({}, resObject); - resObjectWithoutAdvertiserDomains.meta = Object.assign({}, resObject.meta); - delete resObjectWithoutAdvertiserDomains.meta.advertiserDomains; - const bidResponses = { - body: [ resObjectWithoutAdvertiserDomains, resObject ] - } - expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); - }); - it('should return responses which contain empty advertiser domains', function() { - const resObjectWithEmptyAdvertiserDomains = Object.assign({}, resObject); - resObjectWithEmptyAdvertiserDomains.meta = Object.assign({}, resObject.meta); - resObjectWithEmptyAdvertiserDomains.meta.advertiserDomains = []; - const bidResponses = { - body: [ resObjectWithEmptyAdvertiserDomains, resObject ] + + it('should return array of valid video bid responses with mtype', function () { + const serverRequests = spec.buildRequests([bid3], bidderRequest); + const request = serverRequests[0]; + + const ortbResponse = { + body: { + seatbid: [{ + bid: [{ + id: 'bid456', + impid: request.data.imp[0].id, + price: 0.5, + w: 800, + h: 600, + adm: '', + crid: '456def', + mtype: 2, + adomain: ['example.com'] + }] + }], + cur: 'USD' + } + }; + + const serverResponses = spec.interpretResponse(ortbResponse, request); + + expect(serverResponses).to.be.an('array'); + if (serverResponses.length > 0) { + const bidResponse = serverResponses[0]; + expect(bidResponse.mediaType).to.equal('video'); + expect(bidResponse.vastXml).to.be.a('string'); } - expect(spec.interpretResponse(bidResponses)).to.deep.equal([resObjectWithEmptyAdvertiserDomains, resObject]); - }); - it('should skip responses which do not contain meta media type', function() { - const resObjectWithoutMetaMediaType = Object.assign({}, resObject); - resObjectWithoutMetaMediaType.meta = Object.assign({}, resObject.meta); - delete resObjectWithoutMetaMediaType.meta.mediaType; - const bidResponses = { - body: [ resObjectWithoutMetaMediaType, resObject ] + }); + + it('should return array of valid video bid responses with ext.mediaType fallback', function () { + const serverRequests = spec.buildRequests([bid3], bidderRequest); + const request = serverRequests[0]; + + const ortbResponse = { + body: { + seatbid: [{ + bid: [{ + id: 'bid456', + impid: request.data.imp[0].id, + price: 0.5, + w: 800, + h: 600, + adm: '', + crid: '456def', + ext: { + mediaType: 'video' + }, + adomain: ['example.com'] + }] + }], + cur: 'USD' + } + }; + + const serverResponses = spec.interpretResponse(ortbResponse, request); + + expect(serverResponses).to.be.an('array'); + if (serverResponses.length > 0) { + const bidResponse = serverResponses[0]; + expect(bidResponse.mediaType).to.equal('video'); + expect(bidResponse.vastXml).to.be.a('string'); } - expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); }); }); - describe('getUserSyncs', function () { - it('should return trackers for lm(only iframe) if server responses contain lm user sync header and iframe and image enabled', function () { - const serverResponses = [ - { - headers: { - get: function (header) { - if (header === 'x-pll-usersync-image') { - return 'https://tracker-lm.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-lm.ortb.net/sync.html'; - } - } - }, - body: [] + + describe('interpretResponse - mediaType fallback', function() { + const bidderRequest = { + ortb2: { + site: { + page: 'https://example.com/page' } - ]; - const syncOptions = { - iframeEnabled: true, - pixelEnabled: true - }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ - { - type: 'iframe', - url: 'https://tracker-lm.ortb.net/sync.html' + } + }; + + it('should infer mediaType from imp.banner when mtype is missing', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const request = serverRequests[0]; + + const responseWithoutMtype = { + body: { + seatbid: [{ + bid: [{ + id: 'bid123', + impid: request.data.imp[0].id, + price: 0.3, + w: 300, + h: 250, + adm: '

Hello ad

', + crid: '123asd', + adomain: ['example.com'], + exp: 1000 + }] + }], + cur: 'USD' } - ]); + }; + + const serverResponses = spec.interpretResponse(responseWithoutMtype, request); + + expect(serverResponses).to.have.lengthOf(1); + expect(serverResponses[0].mediaType).to.equal('banner'); }); - it('should return empty array if all sync types are disabled', function () { - const serverResponses = [ - { - headers: { - get: function (header) { - if (header === 'x-pll-usersync-image') { - return 'https://tracker-1.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-1.ortb.net/sync.html'; - } - } - }, - body: [] + + it('should use ext.mediaType when available', function() { + const serverRequests = spec.buildRequests([bid1], bidderRequest); + const request = serverRequests[0]; + + const responseWithExtMediaType = { + body: { + seatbid: [{ + bid: [{ + id: 'bid123', + impid: request.data.imp[0].id, + price: 0.3, + w: 300, + h: 250, + adm: '

Hello ad

', + crid: '123asd', + ext: { + mediaType: 'banner' + }, + adomain: ['example.com'], + exp: 1000 + }] + }], + cur: 'USD' } - ]; - const syncOptions = { - iframeEnabled: false, - pixelEnabled: false }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.be.an('array').that.is.empty; + + const serverResponses = spec.interpretResponse(responseWithExtMediaType, request); + + expect(serverResponses).to.have.lengthOf(1); + expect(serverResponses[0].mediaType).to.equal('banner'); }); - it('should return no pixels if iframe sync is enabled and headers are blank', function () { - const serverResponses = [ - { - headers: null, - body: [] - } - ]; - const syncOptions = { - iframeEnabled: true, - pixelEnabled: false + }); + + describe('onBidWon', function() { + it('should replace auction price macro in nurl', function() { + const bid = { + pbMg: 1.23, + nurl: 'https://example.com/win?price=${AUCTION_PRICE}' }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.be.an('array').that.is.empty; + + expect(() => spec.onBidWon(bid)).to.not.throw(); }); - it('should return image sync urls for lm if pixel sync is enabled and headers have lm pixel', function () { - const serverResponses = [ - { - headers: { - get: function (header) { - if (header === 'x-pll-usersync-image') { - return 'https://tracker-lm.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-lm.ortb.net/sync.html'; - } + + it('should handle empty nurl', function() { + const bid = { + pbMg: 1.23, + nurl: '' + }; + + expect(() => spec.onBidWon(bid)).to.not.throw(); + }); + }); + + describe('getUserSyncs', function () { + it('should return iframe sync when available and enabled', function () { + const serverResponses = [{ + headers: { + get: function (header) { + if (header === 'x-pll-usersync-iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; } - }, - body: [] - } - ]; + if (header === 'x-pll-usersync-image') { + return 'https://tracker-lm.ortb.net/sync'; + } + } + }, + body: {} + }]; + const syncOptions = { - iframeEnabled: false, + iframeEnabled: true, pixelEnabled: true }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ - { - type: 'image', - url: 'https://tracker-lm.ortb.net/sync' - } - ]); + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).to.deep.equal([{ + type: 'iframe', + url: 'https://tracker-lm.ortb.net/sync.html' + }]); }); - it('should return image sync urls for client1 and clien2 if pixel sync is enabled and two responses and headers have two pixels', function () { - const serverResponses = [ - { - headers: { - get: function (header) { - if (header === 'x-pll-usersync-image') { - return 'https://tracker-1.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-1.ortb.net/sync.html'; - } + + it('should return image sync when iframe not available', function () { + const serverResponses = [{ + headers: { + get: function (header) { + if (header === 'x-pll-usersync-image') { + return 'https://tracker-lm.ortb.net/sync'; } - }, - body: [] + } }, - { - headers: { - get: function (header) { - if (header === 'x-pll-usersync-image') { - return 'https://tracker-2.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-2.ortb.net/sync.html'; - } - } - }, - body: [] - } - ]; + body: {} + }]; + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ - { - type: 'image', - url: 'https://tracker-1.ortb.net/sync' + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).to.deep.equal([{ + type: 'image', + url: 'https://tracker-lm.ortb.net/sync' + }]); + }); + + it('should return empty array when all sync types disabled', function () { + const serverResponses = [{ + headers: { + get: function (header) { + return 'https://tracker.ortb.net/sync'; + } }, - { - type: 'image', - url: 'https://tracker-2.ortb.net/sync' - } - ]); + body: {} + }]; + + const syncOptions = { + iframeEnabled: false, + pixelEnabled: false + }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).to.be.an('array').that.is.empty; }); - it('should return image sync url for pll if pixel sync is enabled and two responses and headers have two same pixels', function () { + + it('should deduplicate sync URLs', function() { const serverResponses = [ { headers: { get: function (header) { if (header === 'x-pll-usersync-image') { - return 'https://tracker-lm.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-lm.ortb.net/sync.html'; + return 'https://tracker.ortb.net/sync'; } } }, - body: [] + body: {} }, { headers: { get: function (header) { if (header === 'x-pll-usersync-image') { - return 'https://tracker-lm.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-lm.ortb.net/sync.html'; + return 'https://tracker.ortb.net/sync'; } } }, - body: [] + body: {} } ]; + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ - { - type: 'image', - url: 'https://tracker-lm.ortb.net/sync' - } - ]); - }); - it('should return iframe sync url for pll if pixel sync is enabled and iframe is enables and headers have both iframe and img pixels', function () { - const serverResponses = [ - { - headers: { - get: function (header) { - if (header === 'x-pll-usersync-image') { - return 'https://tracker-lm.ortb.net/sync'; - } - if (header === 'x-pll-usersync-iframe') { - return 'https://tracker-lm.ortb.net/sync.html'; - } - } - }, - body: [] - } - ]; - const syncOptions = { - iframeEnabled: true, - pixelEnabled: true - }; - expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ - { - type: 'iframe', - url: 'https://tracker-lm.ortb.net/sync.html' - } - ]); + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).to.have.lengthOf(1); }); }); }); - -function validateAdUnit(adUnit, bid) { - expect(adUnit.id).to.equal(bid.params.adUnitId); - expect(adUnit.bidId).to.equal(bid.bidId); - expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()); - expect(adUnit.transactionId).to.equal(bid.ortb2Imp.ext.tid); - let bidSizes = []; - if (bid.mediaTypes) { - if (bid.mediaTypes.video && bid.mediaTypes.video.playerSize) { - bidSizes = bidSizes.concat([bid.mediaTypes.video.playerSize]); - } - if (bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { - bidSizes = bidSizes.concat(bid.mediaTypes.banner.sizes); - } - } - if (bid.sizes) { - bidSizes = bidSizes.concat(bid.sizes || []); - } - expect(adUnit.sizes).to.deep.equal(bidSizes.map(size => { - return { - width: size[0], - height: size[1] - } - })); - expect(adUnit.publisherId).to.equal(bid.params.publisherId); - expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); - expect(adUnit.supplyChain).to.deep.equal(bid.ortb2.source.ext.schain); - expect(adUnit.ortb2Imp).to.deep.equal(bid.ortb2Imp); -} diff --git a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js index 51ada80b825..869e9eb789c 100644 --- a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js +++ b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js @@ -77,11 +77,11 @@ describe('LiveIntent Analytics Adapter ', () => { events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=y&aun=2') + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=y&aun=2&asz=300x250%2C728x90%2C300x250'); events.emit(EVENTS.BID_WON, BID_WON_EVENT); expect(server.requests.length).to.equal(2); - expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&liip=y'); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&liip=y&asz=728x90'); }); it('request is computed and sent correctly when sampling is 1 and liModule is enabled', () => { @@ -90,11 +90,11 @@ describe('LiveIntent Analytics Adapter ', () => { events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&me=y&liip=y&aun=2') + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&me=y&liip=y&aun=2&asz=300x250%2C728x90%2C300x250') events.emit(EVENTS.BID_WON, BID_WON_EVENT); expect(server.requests.length).to.equal(2); - expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&me=y&liip=y'); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&me=y&liip=y&asz=728x90'); }); it('request is computed and sent correctly when sampling is 1 and liModule is disabled', () => { @@ -103,11 +103,11 @@ describe('LiveIntent Analytics Adapter ', () => { events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&me=n&liip=y&aun=2') + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&me=n&liip=y&aun=2&asz=300x250%2C728x90%2C300x250') events.emit(EVENTS.BID_WON, BID_WON_EVENT); expect(server.requests.length).to.equal(2); - expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&me=n&liip=y'); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&me=n&liip=y&asz=728x90'); }); it('request is computed and sent correctly when sampling is 1 and should forward the correct liTreatmentRate', () => { @@ -116,11 +116,11 @@ describe('LiveIntent Analytics Adapter ', () => { events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&tr=0.95&liip=y&aun=2') + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&tr=0.95&liip=y&aun=2&asz=300x250%2C728x90%2C300x250') events.emit(EVENTS.BID_WON, BID_WON_EVENT); expect(server.requests.length).to.equal(2); - expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&tr=0.95&liip=y'); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&tr=0.95&liip=y&asz=728x90'); }); it('not send any events on auction init if disabled in settings', () => { @@ -137,7 +137,7 @@ describe('LiveIntent Analytics Adapter ', () => { events.emit(EVENTS.BID_WON, BID_WON_EVENT_UNDEFINED); expect(server.requests.length).to.equal(2); - expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=y'); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=y&asz=728x90'); }); it('liip should be n if there is no source or provider in userIdAsEids have the value liveintent.com', () => { @@ -145,7 +145,7 @@ describe('LiveIntent Analytics Adapter ', () => { events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT_NOT_LI); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=n&aun=2'); + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=n&aun=2&asz=300x250%2C728x90'); }); it('no request is computed when sampling is 0', () => { diff --git a/test/spec/modules/locIdSystem_spec.js b/test/spec/modules/locIdSystem_spec.js new file mode 100644 index 00000000000..1b2fc02ddc8 --- /dev/null +++ b/test/spec/modules/locIdSystem_spec.js @@ -0,0 +1,2090 @@ +/** + * This file is licensed under the Apache 2.0 license. + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { locIdSubmodule, storage } from 'modules/locIdSystem.js'; +import { createEidsArray } from 'modules/userId/eids.js'; +import { attachIdSystem } from 'modules/userId/index.js'; +import * as ajax from 'src/ajax.js'; +import { VENDORLESS_GVLID } from 'src/consentHandler.js'; +import { uspDataHandler, gppDataHandler } from 'src/adapterManager.js'; +import { expect } from 'chai/index.mjs'; +import sinon from 'sinon'; + +const TEST_ID = 'SYybozbTuRaZkgGqCD7L7EE0FncoNUcx-om4xTfhJt36TFIAES2tF1qPH'; +const TEST_ENDPOINT = 'https://id.example.com/locid'; +const TEST_CONNECTION_IP = '203.0.113.42'; + +describe('LocID System', () => { + let sandbox; + let ajaxStub; + let ajaxBuilderStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(uspDataHandler, 'getConsentData').returns(null); + sandbox.stub(gppDataHandler, 'getConsentData').returns(null); + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'setDataInLocalStorage'); + ajaxStub = sandbox.stub(); + ajaxBuilderStub = sandbox.stub(ajax, 'ajaxBuilder').returns(ajaxStub); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('module properties', () => { + it('should expose correct module name', () => { + expect(locIdSubmodule.name).to.equal('locId'); + }); + + it('should have eids configuration with correct defaults', () => { + expect(locIdSubmodule.eids).to.be.an('object'); + expect(locIdSubmodule.eids.locId).to.be.an('object'); + expect(locIdSubmodule.eids.locId.source).to.equal('locid.com'); + // atype 1 = AdCOM AgentTypeWeb + expect(locIdSubmodule.eids.locId.atype).to.equal(1); + }); + + it('should register as a vendorless TCF module', () => { + expect(locIdSubmodule.gvlid).to.equal(VENDORLESS_GVLID); + }); + + it('should have getValue function that extracts ID', () => { + const getValue = locIdSubmodule.eids.locId.getValue; + expect(getValue('test-id')).to.equal('test-id'); + expect(getValue({ id: 'id-shape' })).to.equal('id-shape'); + expect(getValue({ locId: 'test-id' })).to.equal('test-id'); + expect(getValue({ locid: 'legacy-id' })).to.equal('legacy-id'); + }); + }); + + describe('decode', () => { + it('should decode valid ID correctly', () => { + const result = locIdSubmodule.decode({ id: TEST_ID, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.deep.equal({ locId: TEST_ID }); + }); + + it('should decode ID passed as object', () => { + const result = locIdSubmodule.decode({ id: TEST_ID, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.deep.equal({ locId: TEST_ID }); + }); + + it('should return undefined for invalid values', () => { + [null, undefined, '', {}, [], 123, TEST_ID].forEach(value => { + expect(locIdSubmodule.decode(value)).to.be.undefined; + }); + }); + + it('should return undefined when connection_ip is missing', () => { + expect(locIdSubmodule.decode({ id: TEST_ID })).to.be.undefined; + }); + + it('should return undefined for IDs exceeding max length', () => { + const longId = 'a'.repeat(513); + expect(locIdSubmodule.decode({ id: longId, connectionIp: TEST_CONNECTION_IP })).to.be.undefined; + }); + + it('should accept ID at exactly MAX_ID_LENGTH (512 characters)', () => { + const maxLengthId = 'a'.repeat(512); + const result = locIdSubmodule.decode({ id: maxLengthId, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.deep.equal({ locId: maxLengthId }); + }); + }); + + describe('getId', () => { + it('should return callback for async endpoint fetch', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + const result = locIdSubmodule.getId(config, {}); + expect(result).to.have.property('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('should call endpoint and return tx_cloc on success', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + expect(ajaxStub.calledOnce).to.be.true; + done(); + }); + }); + + it('should cache entry with null id when tx_cloc is missing but connection_ip is present', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ stable_cloc: 'stable-cloc-id-12345', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should prefer tx_cloc over stable_cloc when both present', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ + tx_cloc: TEST_ID, + stable_cloc: 'stable-fallback', + connection_ip: TEST_CONNECTION_IP + })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return undefined on endpoint error', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.error('Network error'); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + + it('should reuse storedId when valid and IP cache matches', () => { + // Set up IP cache matching stored entry's IP + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + name: '_locid' + } + }; + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + updatedAt: Date.now(), + expiresAt: Date.now() + 1000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(ajaxStub.called).to.be.false; + }); + + it('should not reuse storedId when expired', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + const storedId = { + id: 'expired-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: 1000, + updatedAt: 1000, + expiresAt: Date.now() - 1000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.have.property('callback'); + }); + + it('should not reuse storedId when connectionIp is missing', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + const storedId = { + id: 'existing-id', + createdAt: 1000, + updatedAt: 1000, + expiresAt: 2000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.have.property('callback'); + }); + + it('should pass x-api-key header when apiKey is configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.customHeaders).to.deep.equal({ 'x-api-key': 'test-api-key' }); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + apiKey: 'test-api-key' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should not include customHeaders when apiKey is not configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.customHeaders).to.not.exist; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should pass withCredentials when configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.withCredentials).to.be.true; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + withCredentials: true + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should default withCredentials to false', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.withCredentials).to.be.false; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should use default timeout of 800ms when timeoutMs is not configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + expect(ajaxBuilderStub.calledWith(800)).to.be.true; + done(); + }); + }); + + it('should use custom timeout when timeoutMs is configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + timeoutMs: 1500 + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + expect(ajaxBuilderStub.calledWith(1500)).to.be.true; + done(); + }); + }); + + it('should always use GET method', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.method).to.equal('GET'); + expect(body).to.be.null; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should append alt_id query parameter when altId is configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT + '?alt_id=user123'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + altId: 'user123' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should use & separator when endpoint already has query params', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT + '?existing=param&alt_id=user456'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + '?existing=param', + altId: 'user456' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should URL-encode altId value', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT + '?alt_id=user%40example.com'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + altId: 'user@example.com' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should not append alt_id when altId is not configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should preserve URL fragment when appending alt_id', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal('https://id.example.com/locid?alt_id=user123#frag'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: 'https://id.example.com/locid#frag', + altId: 'user123' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should preserve URL fragment when endpoint has existing query params', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal('https://id.example.com/locid?x=1&alt_id=user456#frag'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: 'https://id.example.com/locid?x=1#frag', + altId: 'user456' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should return undefined via callback when endpoint is empty string', (done) => { + const config = { + params: { + endpoint: '' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + expect(ajaxStub.called).to.be.false; + done(); + }); + }); + }); + + describe('extendId', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + it('should return stored id when valid and IP cache is current', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 14400000 + })); + const storedId = { id: 'existing-id', connectionIp: TEST_CONNECTION_IP }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should reuse storedId when refreshInSeconds is configured but not due', () => { + const now = Date.now(); + const refreshInSeconds = 60; + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: now, + expiresAt: now + 14400000 + })); + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: now - ((refreshInSeconds - 10) * 1000), + expiresAt: now + 10000 + }; + const refreshConfig = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + refreshInSeconds + } + }; + const result = locIdSubmodule.extendId(refreshConfig, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should return undefined when refreshInSeconds is due', () => { + const now = Date.now(); + const refreshInSeconds = 60; + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: now - ((refreshInSeconds + 10) * 1000), + expiresAt: now + 10000 + }; + const refreshConfig = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + refreshInSeconds + } + }; + const result = locIdSubmodule.extendId(refreshConfig, {}, storedId); + expect(result).to.be.undefined; + }); + + it('should return undefined when refreshInSeconds is configured and createdAt is missing', () => { + const now = Date.now(); + const refreshInSeconds = 60; + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + expiresAt: now + 10000 + }; + const refreshConfig = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + refreshInSeconds + } + }; + const result = locIdSubmodule.extendId(refreshConfig, {}, storedId); + expect(result).to.be.undefined; + }); + + it('should return undefined when storedId is a string', () => { + const result = locIdSubmodule.extendId(config, {}, 'existing-id'); + expect(result).to.be.undefined; + }); + + it('should return undefined when connectionIp is missing', () => { + const result = locIdSubmodule.extendId(config, {}, { id: 'existing-id' }); + expect(result).to.be.undefined; + }); + + it('should return undefined when stored entry is expired', () => { + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + expiresAt: Date.now() - 1000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.be.undefined; + }); + }); + + describe('privacy framework handling', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + // --- Tests for no privacy signals (LI-based operation) --- + // LocID operates under Legitimate Interest and should proceed when no + // privacy framework is present, unless requirePrivacySignals is enabled. + + it('should proceed when no consentData provided at all (default LI-based operation)', () => { + // When no privacy signals are present, module should proceed by default + const result = locIdSubmodule.getId(config, undefined); + expect(result).to.have.property('callback'); + }); + + it('should proceed when consentData is null (default LI-based operation)', () => { + const result = locIdSubmodule.getId(config, null); + expect(result).to.have.property('callback'); + }); + + it('should proceed when consentData is empty object (default LI-based operation)', () => { + // Empty object has no privacy signals, so should proceed + const result = locIdSubmodule.getId(config, {}); + expect(result).to.have.property('callback'); + }); + + it('should return undefined when no consentData and requirePrivacySignals=true', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const result = locIdSubmodule.getId(strictConfig, undefined); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined when empty consentData and requirePrivacySignals=true', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const result = locIdSubmodule.getId(strictConfig, {}); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined when no consentData and privacyMode=requireSignals', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + privacyMode: 'requireSignals' + } + }; + const result = locIdSubmodule.getId(strictConfig, undefined); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should proceed when no consentData and privacyMode=allowWithoutSignals', () => { + const permissiveConfig = { + params: { + endpoint: TEST_ENDPOINT, + privacyMode: 'allowWithoutSignals' + } + }; + const result = locIdSubmodule.getId(permissiveConfig, undefined); + expect(result).to.have.property('callback'); + }); + + it('should proceed with privacy signals present and requirePrivacySignals=true', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: {} + } + }; + const result = locIdSubmodule.getId(strictConfig, consentData); + expect(result).to.have.property('callback'); + }); + + it('should detect privacy signals from uspDataHandler and block on processing restriction', () => { + // Restore and re-stub to return USP processing restriction signal + uspDataHandler.getConsentData.restore(); + sandbox.stub(uspDataHandler, 'getConsentData').returns('1YY-'); + + // Even with empty consentData, handler provides privacy signal + const result = locIdSubmodule.getId(config, {}); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should detect privacy signals from gppDataHandler and block on processing restriction', () => { + // Restore and re-stub to return GPP processing restriction signal + gppDataHandler.getConsentData.restore(); + sandbox.stub(gppDataHandler, 'getConsentData').returns({ + applicableSections: [7], + parsedSections: { + usnat: { + KnownChildSensitiveDataConsents: [1] + } + } + }); + + // Even with empty consentData, handler provides privacy signal + const result = locIdSubmodule.getId(config, {}); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should proceed when uspDataHandler returns non-restrictive value', () => { + // Restore and re-stub to return non-restrictive USP + uspDataHandler.getConsentData.restore(); + sandbox.stub(uspDataHandler, 'getConsentData').returns('1NN-'); + + const result = locIdSubmodule.getId(config, {}); + expect(result).to.have.property('callback'); + }); + + // --- Tests for gdprApplies edge cases (LI-based operation) --- + // gdprApplies alone does NOT constitute a privacy signal. + // A GDPR signal requires actual CMP artifacts (consentString or vendorData). + + it('should proceed when gdprApplies=true but no consentString/vendorData (default LI mode)', () => { + // gdprApplies alone is not a signal - allows LI-based operation without CMP + const consentData = { + gdprApplies: true + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when gdprApplies=true and usp is present but no GDPR CMP artifacts', () => { + const consentData = { + gdprApplies: true, + usp: '1NN-' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when gdprApplies=true and consentString is empty (treated as absent CMP artifact)', () => { + // Empty consentString is treated as not present, so LI-mode behavior applies. + const consentData = { + gdprApplies: true, + consentString: '' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should return undefined when gdprApplies=true, no CMP artifacts, and strict mode', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const consentData = { + gdprApplies: true + }; + const result = locIdSubmodule.getId(strictConfig, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined when gdprApplies=true, vendorData present but no consentString', () => { + // vendorData presence = CMP is present, so signals ARE present + // GDPR applies + signals present + no consentString → deny + const consentData = { + gdprApplies: true, + vendorData: { + vendor: {} + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should deny when gdprApplies=false and strict mode is enabled (gdprApplies alone is not a signal)', () => { + // gdprApplies=false alone is not a signal, so strict mode blocks + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const consentData = { + gdprApplies: false + }; + const result = locIdSubmodule.getId(strictConfig, consentData); + // gdprApplies=false is not a signal, so strict mode denies + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + // --- Original tests for when privacy signals ARE present --- + + it('should proceed with valid GDPR framework data', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR does not apply (no CMP artifacts)', () => { + // gdprApplies=false alone is not a signal - LI operation allowed + const consentData = { + gdprApplies: false + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies with consentString and vendor flags deny', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: { 999: false } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should return undefined on US Privacy processing restriction and not call ajax', () => { + const consentData = { + usp: '1YY-' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined on legacy US Privacy processing restriction and not call ajax', () => { + const consentData = { + uspConsent: '1YY-' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined on GPP processing restriction and not call ajax', () => { + const consentData = { + gpp: { + applicableSections: [7], + parsedSections: { + usnat: { + KnownChildSensitiveDataConsents: [1] + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined on legacy GPP processing restriction and not call ajax', () => { + const consentData = { + gppConsent: { + applicableSections: [7], + parsedSections: { + usnat: { + KnownChildSensitiveDataConsents: [1] + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should proceed when nested GDPR applies but no CMP artifacts (LI operation)', () => { + // Empty consentString in nested gdpr object is not a signal - LI operation allowed + const consentData = { + gdpr: { + gdprApplies: true, + consentString: '' + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed with valid nested GDPR framework data', () => { + const consentData = { + gdpr: { + gdprApplies: true, + consentString: 'valid-consent-string' + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendorData is present', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: { 999: false } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendor is missing from consents', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 1234: true }, + legitimateInterests: { 1234: true } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendor consents object is present', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: true } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendor legitimate interests object is present', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: { 999: true } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendorData is not available (cannot determine vendor permission)', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should check nested vendorData when using gdpr object shape', () => { + const consentData = { + gdpr: { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: true } + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when nested vendorData has explicit deny flags', () => { + const consentData = { + gdpr: { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: {} + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendor consents is a function returning true', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: (id) => id === 999, + legitimateInterests: {} + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendor legitimateInterests is a function returning true', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: (id) => false, + legitimateInterests: (id) => id === 999 + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendor consent callbacks both return false', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: (id) => false, + legitimateInterests: (id) => false + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + }); + + describe('response parsing', () => { + it('should parse JSON string response', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success('{"tx_cloc":"parsed-id","connection_ip":"203.0.113.42"}'); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal('parsed-id'); + expect(id.connectionIp).to.equal('203.0.113.42'); + done(); + }); + }); + + it('should return undefined when connection_ip is missing', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + + it('should return undefined for invalid JSON', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success('not valid json'); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + + it('should cache entry with null id when tx_cloc is empty string', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: '', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should cache entry with null id when tx_cloc is whitespace-only', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: ' \n\t ', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return undefined when tx_cloc is missing', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ other_field: 'value' })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + }); + + describe('empty tx_cloc handling', () => { + it('should decode null id as undefined (no EID emitted)', () => { + const result = locIdSubmodule.decode({ id: null, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.be.undefined; + }); + + it('should cache empty tx_cloc response for the full cache period', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { expires: 7 } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + expect(id.expiresAt).to.be.a('number'); + expect(id.expiresAt).to.be.greaterThan(Date.now()); + done(); + }); + }); + + it('should reuse cached entry with null id on subsequent getId calls', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: null, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + updatedAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(ajaxStub.called).to.be.false; + }); + + it('should not produce EID when tx_cloc is null', () => { + const decoded = locIdSubmodule.decode({ id: null, connectionIp: TEST_CONNECTION_IP }); + expect(decoded).to.be.undefined; + }); + + it('should write IP cache when endpoint returns empty tx_cloc', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + expect(storage.setDataInLocalStorage.called).to.be.true; + const callArgs = storage.setDataInLocalStorage.getCall(0).args; + expect(callArgs[0]).to.equal('_locid_ip'); + const ipEntry = JSON.parse(callArgs[1]); + expect(ipEntry.ip).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + }); + + describe('IP cache management', () => { + const ipCacheConfig = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + it('should read valid IP cache entry', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + // If IP cache is valid and stored entry matches, should reuse + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(ipCacheConfig, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(ajaxStub.called).to.be.false; + }); + + it('should treat expired IP cache as missing', (done) => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now() - 20000, + expiresAt: Date.now() - 1000 + })); + + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(ipCacheConfig, {}, storedId); + // Expired IP cache → calls main endpoint + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should handle corrupted IP cache JSON gracefully', (done) => { + storage.getDataFromLocalStorage.returns('not-valid-json'); + + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const result = locIdSubmodule.getId(ipCacheConfig, {}); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should derive IP cache key from storage name', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: 'custom_key' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + const setCall = storage.setDataInLocalStorage.getCall(0); + expect(setCall.args[0]).to.equal('custom_key_ip'); + done(); + }); + }); + + it('should use custom ipCacheName when configured', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT, ipCacheName: 'my_ip_cache' }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + const setCall = storage.setDataInLocalStorage.getCall(0); + expect(setCall.args[0]).to.equal('my_ip_cache'); + done(); + }); + }); + + it('should use custom ipCacheTtlMs when configured', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT, ipCacheTtlMs: 7200000 }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + const setCall = storage.setDataInLocalStorage.getCall(0); + const ipEntry = JSON.parse(setCall.args[1]); + // TTL should be ~2 hours + const ttl = ipEntry.expiresAt - ipEntry.fetchedAt; + expect(ttl).to.equal(7200000); + done(); + }); + }); + + it('should write IP cache on every successful main endpoint response', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '10.0.0.1' })); + }); + + const result = locIdSubmodule.getId(ipCacheConfig, {}); + result.callback(() => { + expect(storage.setDataInLocalStorage.called).to.be.true; + const ipEntry = JSON.parse(storage.setDataInLocalStorage.getCall(0).args[1]); + expect(ipEntry.ip).to.equal('10.0.0.1'); + done(); + }); + }); + }); + + describe('getId with ipEndpoint (two-call optimization)', () => { + it('should call ipEndpoint first when IP cache is expired', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success(JSON.stringify({ ip: '10.0.0.1' })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '10.0.0.1' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + // IP changed (no stored entry) → 2 calls: ipEndpoint + main endpoint + expect(callCount).to.equal(2); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should reuse cached tx_cloc when ipEndpoint returns same IP', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success(JSON.stringify({ ip: TEST_CONNECTION_IP })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: 'new-id', connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP unchanged → only 1 call (ipEndpoint), reuses stored tx_cloc + expect(callCount).to.equal(1); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should call main endpoint when ipEndpoint returns different IP', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success(JSON.stringify({ ip: '10.0.0.99' })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: 'new-id', connection_ip: '10.0.0.99' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP changed → 2 calls: ipEndpoint + main endpoint + expect(callCount).to.equal(2); + expect(id.id).to.equal('new-id'); + done(); + }); + }); + + it('should fall back to main endpoint when ipEndpoint fails', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.error('Network error'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(callCount).to.equal(2); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should fall back to main endpoint when ipEndpoint returns invalid IP', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success('not-an-ip!!!'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(callCount).to.equal(2); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should pass apiKey header to ipEndpoint when configured', (done) => { + ajaxStub.callsFake((url, callbacks, _body, options) => { + if (url === 'https://ip.example.com/check') { + expect(options.customHeaders).to.deep.equal({ 'x-api-key': 'test-key-123' }); + callbacks.success(JSON.stringify({ ip: TEST_CONNECTION_IP })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check', + apiKey: 'test-key-123' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should parse plain text IP from ipEndpoint', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success(TEST_CONNECTION_IP); + } else { + callbacks.success(JSON.stringify({ tx_cloc: 'new-id', connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP same → reuses stored tx_cloc + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + }); + + describe('getId tx_cloc preservation (no churn)', () => { + it('should preserve existing tx_cloc when main endpoint returns same IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP unchanged → preserve existing tx_cloc (don't churn) + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should use fresh non-null tx_cloc when stored entry is null for same IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: null, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should use fresh tx_cloc when main endpoint returns different IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: '10.0.0.99' })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP changed → use fresh tx_cloc + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal('10.0.0.99'); + done(); + }); + }); + + it('should use fresh tx_cloc when stored entry is expired even if IP matches', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now() - 86400000, + expiresAt: Date.now() - 1000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // tx_cloc expired → use fresh even though IP matches + expect(id.id).to.equal('fresh-id'); + done(); + }); + }); + + it('should honor null tx_cloc from main endpoint even when stored entry is reusable', (done) => { + // Server returns connection_ip but no tx_cloc → freshEntry.id === null. + // The stored entry has a valid tx_cloc for the same IP, but the server + // now indicates no ID for this IP. The null response must be honored. + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // Server said no tx_cloc → stored entry must NOT be preserved + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should use fresh entry on first load (no stored entry)', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + }); + + describe('extendId with null id and IP cache', () => { + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + it('should accept stored entry with null id (empty tx_cloc)', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 14400000 + })); + const storedId = { + id: null, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should return refresh callback when IP cache shows different IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: '10.0.0.99' })); + }); + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: '10.0.0.99', + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal('10.0.0.99'); + done(); + }); + }); + + it('should extend when IP cache matches stored entry IP', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should return refresh callback when IP cache is missing', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return refresh callback when IP cache is expired', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now() - 14400000, + expiresAt: Date.now() - 1000 + })); + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return undefined for undefined id (not null, not valid string)', () => { + const storedId = { + id: undefined, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.be.undefined; + }); + }); + + describe('normalizeStoredId with null id', () => { + it('should preserve explicit null id', () => { + // getId is the public interface; test via decode which uses normalized values + const stored = { id: null, connectionIp: TEST_CONNECTION_IP }; + // decode returns undefined for null id (correct: no EID emitted) + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.be.undefined; + }); + + it('should preserve valid string id', () => { + const stored = { id: TEST_ID, connectionIp: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + }); + + it('should fall back to tx_cloc when id key is absent', () => { + const stored = { tx_cloc: TEST_ID, connectionIp: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + }); + + it('should handle connection_ip alias', () => { + const stored = { id: TEST_ID, connection_ip: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + }); + }); + + describe('parseIpResponse via ipEndpoint', () => { + it('should parse JSON with ip field', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success('{"ip":"1.2.3.4"}'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '1.2.3.4' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + // IP fetched and used + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should parse JSON with connection_ip field', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success('{"connection_ip":"1.2.3.4"}'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '1.2.3.4' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should parse plain text IP address', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success('1.2.3.4\n'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '1.2.3.4' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + }); + + describe('backward compatibility', () => { + it('should work with existing stored entries that have string ids', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should work with stored entries using connection_ip alias', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connection_ip: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result.id.connectionIp).to.equal(TEST_CONNECTION_IP); + }); + + it('should work with stored entries using tx_cloc alias', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + tx_cloc: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result.id.id).to.equal(TEST_ID); + }); + }); + + describe('EID round-trip integration', () => { + before(() => { + attachIdSystem(locIdSubmodule); + }); + + it('should produce a valid EID from decode output via createEidsArray', () => { + // Simulate the full Prebid pipeline: + // 1. decode() returns { locId: "string" } + // 2. Prebid extracts idObj["locId"] -> the string + // 3. createEidsArray({ locId: "string" }) should produce a valid EID + const stored = { id: TEST_ID, connectionIp: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + + const eids = createEidsArray(decoded); + expect(eids.length).to.equal(1); + expect(eids[0]).to.deep.equal({ + source: 'locid.com', + uids: [{ id: TEST_ID, atype: 1 }] + }); + expect(eids[0].uids[0].atype).to.not.equal(Number('33' + '84')); + }); + + it('should not produce EID when decode returns undefined', () => { + const decoded = locIdSubmodule.decode(null); + expect(decoded).to.be.undefined; + }); + + it('should not produce EID when only stable_cloc is present', () => { + const decoded = locIdSubmodule.decode({ + stable_cloc: 'stable-only-value', + connectionIp: TEST_CONNECTION_IP + }); + expect(decoded).to.be.undefined; + + const eids = createEidsArray({ locId: decoded?.locId }); + expect(eids).to.deep.equal([]); + }); + + it('should not produce EID when tx_cloc is null', () => { + const decoded = locIdSubmodule.decode({ id: null, connectionIp: TEST_CONNECTION_IP }); + expect(decoded).to.be.undefined; + + const eids = createEidsArray({ locId: decoded?.locId }); + expect(eids).to.deep.equal([]); + }); + + it('should not produce EID when tx_cloc is missing', () => { + const decoded = locIdSubmodule.decode({ connectionIp: TEST_CONNECTION_IP }); + expect(decoded).to.be.undefined; + + const eids = createEidsArray({ locId: decoded?.locId }); + expect(eids).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index 79c9c0d6172..a0b4a02cc57 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -432,7 +432,7 @@ describe('LunamediaHBBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -441,9 +441,7 @@ describe('LunamediaHBBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookie.lmgssp.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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -452,7 +450,7 @@ describe('LunamediaHBBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index 30c68601767..c8a0109a94b 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -32,13 +32,15 @@ describe('marsmedia adapter tests', function () { }; win = { document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600 + } }, location: { href: 'http://location' }, - innerWidth: 800, - innerHeight: 600 }; this.defaultBidderRequest = { 'refererInfo': { @@ -506,6 +508,8 @@ describe('marsmedia adapter tests', function () { context('when element is fully in view', function() { it('returns 100', function() { + sandbox.stub(internal, 'getWindowTop').returns(win); + resetWinDimensions(); Object.assign(element, { width: 600, height: 400 }); const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(request.data); @@ -530,7 +534,6 @@ describe('marsmedia adapter tests', function () { const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(request.data); expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); - internal.getWindowTop.restore(); }); }); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 05336197872..21b7b4a7d21 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -432,7 +432,7 @@ describe('MathildeAdsBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -441,9 +441,7 @@ describe('MathildeAdsBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs2.mathilde-ads.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -452,7 +450,7 @@ describe('MathildeAdsBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js index 6a1e588e886..8c4c6bc0f3a 100644 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ b/test/spec/modules/mediagoBidAdapter_spec.js @@ -200,7 +200,7 @@ describe('mediago:BidAdapterTests', function () { bid: [ { id: '6e28cfaf115a354ea1ad8e1304d6d7b8', - impid: '1', + impid: '54d73f19c9d47a', price: 0.087581, adm: adm, cid: '1339145', diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index caa22f4da1d..962070e0e22 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -296,4 +296,25 @@ describe('MediaSquare bid adapter tests', function () { expect(bid).to.have.property('renderer'); delete BID_RESPONSE.body.responses[0].video; }); + it('Verifies burls in bid response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].burls = [{'url': 'http://myburl.com/track?bid=1.0'}]; + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid.mediasquare).to.have.property('burls'); + expect(bid.mediasquare.burls).to.have.lengthOf(1); + expect(bid.mediasquare.burls[0]).to.have.property('url').and.to.equal('http://myburl.com/track?bid=1.0'); + delete BID_RESPONSE.body.responses[0].burls; + }); + it('Verifies burls bidwon', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].burls = [{'url': 'http://myburl.com/track?bid=1.0'}]; + const response = spec.interpretResponse(BID_RESPONSE, request); + const won = spec.onBidWon(response[0]); + expect(won).to.equal(true); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('http://myburl.com/track?bid=1.0'); + delete BID_RESPONSE.body.responses[0].burls; + }); }); diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js index 7fd41a3c4c5..b3f160b1530 100644 --- a/test/spec/modules/mgidRtdProvider_spec.js +++ b/test/spec/modules/mgidRtdProvider_spec.js @@ -33,7 +33,7 @@ describe('Mgid RTD submodule', () => { expect(mgidSubmodule.init({})).to.be.false; }); - it('getBidRequestData send all params to our endpoint and succesfully modifies ortb2', () => { + it('getBidRequestData send all params to our endpoint and successfully modifies ortb2', () => { const responseObj = { userSegments: ['100', '200'], userSegtax: 5, diff --git a/test/spec/modules/mileBidAdapter_spec.js b/test/spec/modules/mileBidAdapter_spec.js new file mode 100644 index 00000000000..34171f86114 --- /dev/null +++ b/test/spec/modules/mileBidAdapter_spec.js @@ -0,0 +1,691 @@ +import { expect } from 'chai'; +import { spec, siteIdTracker, publisherIdTracker } from 'modules/mileBidAdapter.js'; +import { BANNER } from 'src/mediaTypes.js'; +import * as ajax from 'src/ajax.js'; +import * as utils from 'src/utils.js'; + +describe('mileBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid; + + beforeEach(function () { + bid = { + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + }); + + it('should return true when all required params are present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when placementId is missing', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when siteId is missing', function () { + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when publisherId is missing', function () { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when params is missing', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when params is null', function () { + bid.params = null; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let validBidRequests, bidderRequest; + + beforeEach(function () { + validBidRequests = [{ + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + adUnitCode: 'test-ad-unit', + bidId: 'bid123', + ortb2Imp: { + ext: { + gpid: '/test/ad/unit' + } + } + }]; + + bidderRequest = { + bidderCode: 'mile', + bidderRequestId: 'bidderReq123', + auctionId: 'auction123', + timeout: 3000, + refererInfo: { + page: 'https://example.com/page', + domain: 'example.com', + ref: 'https://google.com' + }, + ortb2: { + source: { + tid: 'transaction123' + } + } + }; + }); + + it('should return a valid server request object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://pbs.atmtd.com/mile/v1/request'); + expect(request.data).to.be.an('object'); + }); + + it('should build OpenRTB 2.5 compliant request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = request.data; + + expect(data.id).to.equal('bidderReq123'); + expect(data.imp).to.be.an('array').with.lengthOf(1); + expect(data.tmax).to.equal(3000); + expect(data.cur).to.deep.equal(['USD']); + expect(data.site).to.be.an('object'); + expect(data.device).to.be.an('object'); + expect(data.source).to.be.an('object'); + }); + + it('should include imp object with correct structure', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const imp = request.data.imp[0]; + + expect(imp.id).to.equal('bid123'); + expect(imp.tagid).to.equal('12345'); + expect(imp.secure).to.equal(1); + expect(imp.banner).to.be.an('object'); + expect(imp.banner.format).to.be.an('array').with.lengthOf(2); + expect(imp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); + expect(imp.banner.format[1]).to.deep.equal({ w: 728, h: 90 }); + }); + + it('should include ext fields in imp object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const imp = request.data.imp[0]; + + expect(imp.ext.adUnitCode).to.equal('test-ad-unit'); + expect(imp.ext.placementId).to.equal('12345'); + expect(imp.ext.gpid).to.equal('/test/ad/unit'); + }); + + it('should include site object with publisher info', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const site = request.data.site; + + expect(site.id).to.equal('site123'); + expect(site.page).to.equal('https://example.com/page'); + expect(site.domain).to.equal('example.com'); + expect(site.ref).to.equal('https://google.com'); + expect(site.publisher.id).to.equal('pub456'); + }); + + it('should include device object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const device = request.data.device; + + expect(device.ua).to.be.a('string'); + expect(device.language).to.be.a('string'); + expect(device.dnt).to.be.a('number'); + }); + + it('should include source object with tid', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const source = request.data.source; + + expect(source.tid).to.equal('transaction123'); + }); + + it('should include bidfloor when floor price is available', function () { + validBidRequests[0].getFloor = function() { + return { floor: 0.5, currency: 'USD' }; + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const imp = request.data.imp[0]; + + expect(imp.bidfloor).to.equal(0.5); + expect(imp.bidfloorcur).to.equal('USD'); + }); + + it('should include GDPR consent when present', function () { + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent-string-123' + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.user.ext.consent).to.equal('consent-string-123'); + }); + + it('should include US Privacy consent when present', function () { + bidderRequest.uspConsent = '1YNN'; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.regs.ext.us_privacy).to.equal('1YNN'); + }); + + it('should include GPP consent when present', function () { + bidderRequest.gppConsent = { + gppString: 'gpp-string-123', + applicableSections: [1, 2, 3] + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.regs.gpp).to.equal('gpp-string-123'); + expect(request.data.regs.gpp_sid).to.deep.equal([1, 2, 3]); + }); + + it('should include user EIDs when present', function () { + validBidRequests[0].userIdAsEids = [ + { + source: 'pubcid.org', + uids: [{ id: 'user-id-123' }] + } + ]; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.user.ext.eids).to.be.an('array').with.lengthOf(1); + expect(request.data.user.ext.eids[0].source).to.equal('pubcid.org'); + }); + + it('should include supply chain when present', function () { + validBidRequests[0].ortb2 = { + source: { + ext: { + schain: { + ver: '1.0', + complete: 1, + nodes: [] + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.source.ext.schain).to.be.an('object'); + expect(request.data.source.ext.schain.ver).to.equal('1.0'); + }); + + it('should handle multiple bid requests with same siteId and publisherId', function () { + const secondBid = { + ...validBidRequests[0], + bidId: 'bid456', + params: { + placementId: '67890', + siteId: 'site123', + publisherId: 'pub456' + } + }; + + const request = spec.buildRequests([validBidRequests[0], secondBid], bidderRequest); + + expect(request.data.imp).to.be.an('array').with.lengthOf(2); + expect(request.data.imp[0].id).to.equal('bid123'); + expect(request.data.imp[1].id).to.equal('bid456'); + }); + + it('should reject bids with different siteId', function () { + const firstBid = { + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const secondBid = { + bidder: 'mile', + params: { + placementId: '67890', + siteId: 'differentSite', // Different siteId + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + // First bid should be valid + expect(spec.isBidRequestValid(firstBid)).to.be.true; + + // Second bid should be rejected due to siteId mismatch + expect(spec.isBidRequestValid(secondBid)).to.be.false; + }); + + it('should reject bids with different publisherId', function () { + const firstBid = { + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const secondBid = { + bidder: 'mile', + params: { + placementId: '67890', + siteId: 'site123', + publisherId: 'differentPub' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + // First bid should be valid + expect(spec.isBidRequestValid(firstBid)).to.be.true; + + // Second bid should be rejected due to publisherId mismatch + expect(spec.isBidRequestValid(secondBid)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + let serverResponse; + + beforeEach(function () { + serverResponse = { + body: { + site: { + id: 'site123', + domain: 'example.com', + publisher: { + id: 'pub456' + }, + page: 'https://example.com/page', + }, + cur: 'USD', + bids: [ + { + requestId: 'bid123', + cpm: 1.5, + width: 300, + height: 250, + ad: '
test ad
', + creativeId: 'creative123', + ttl: 300, + nurl: 'https://example.com/win?price=${AUCTION_PRICE}', + adomain: ['advertiser.com'], + upstreamBidder: 'upstreamBidder' + } + ] + } + }; + }); + + it('should return an array of bid responses', function () { + const bids = spec.interpretResponse(serverResponse); + + expect(bids).to.be.an('array').with.lengthOf(1); + }); + + it('should parse bid response correctly', function () { + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.requestId).to.equal('bid123'); + expect(bid.cpm).to.equal(1.5); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.equal('
test ad
'); + expect(bid.creativeId).to.equal('creative123'); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.netRevenue).to.be.true; + expect(bid.mediaType).to.equal(BANNER); + expect(bid.meta.upstreamBidder).to.equal('upstreamBidder'); + expect(bid.meta.siteUID).to.equal('site123'); + expect(bid.meta.publisherID).to.equal('pub456'); + expect(bid.meta.page).to.equal('https://example.com/page'); + expect(bid.meta.domain).to.equal('example.com'); + }); + + it('should include nurl in bid response', function () { + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.nurl).to.equal('https://example.com/win?price=${AUCTION_PRICE}'); + }); + + it('should include meta.advertiserDomains', function () { + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + }); + + it('should handle empty response', function () { + const bids = spec.interpretResponse({ body: null }); + + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should handle response with no bids', function () { + serverResponse.body.bids = []; + const bids = spec.interpretResponse(serverResponse); + + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should handle alternative field names (w/h instead of width/height)', function () { + serverResponse.body.bids[0] = { + requestId: 'bid123', + cpm: 1.5, + w: 728, + h: 90, + ad: '
test ad
' + }; + + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.width).to.equal(728); + expect(bid.height).to.equal(90); + }); + + it('should use default currency if not specified', function () { + delete serverResponse.body.cur; + const bids = spec.interpretResponse(serverResponse); + + expect(bids[0].currency).to.equal('USD'); + }); + + it('should handle response with no site or publisher', function () { + delete serverResponse.body.site; + delete serverResponse.body.publisher; + const bids = spec.interpretResponse(serverResponse); + + expect(bids[0].meta.siteUID).to.be.empty; + expect(bids[0].meta.publisherID).to.be.empty; + expect(bids[0].meta.page).to.be.empty; + expect(bids[0].meta.domain).to.be.empty; + }); + }); + + describe('getUserSyncs', function () { + let syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent; + + beforeEach(function () { + syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + serverResponses = []; + }); + + it('should return iframe sync when enabled', function () { + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include('https://scripts.atmtd.com/user-sync/load-cookie.html'); + }); + + it('should not return syncs when iframe is disabled', function () { + syncOptions.iframeEnabled = false; + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.be.an('array').with.lengthOf(0); + }); + + it('should include GDPR consent params', function () { + gdprConsent = { + gdprApplies: true, + consentString: 'consent-string-123' + }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=consent-string-123'); + }); + + it('should include US Privacy consent param', function () { + uspConsent = '1YNN'; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, null, uspConsent); + + expect(syncs[0].url).to.include('us_privacy=1YNN'); + }); + + it('should include GPP consent params', function () { + gppConsent = { + gppString: 'gpp-string-123', + applicableSections: [1, 2, 3] + }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, null, null, gppConsent); + + expect(syncs[0].url).to.include('gpp=gpp-string-123'); + expect(syncs[0].url).to.include('gpp_sid=1%2C2%2C3'); + }); + + it('should include all consent params when present', function () { + gdprConsent = { gdprApplies: true, consentString: 'gdpr-consent' }; + uspConsent = '1YNN'; + gppConsent = { gppString: 'gpp-string', applicableSections: [1] }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('us_privacy=1YNN'); + expect(syncs[0].url).to.include('gpp=gpp-string'); + }); + }); + + describe('onBidWon', function () { + let bid, ajaxStub; + + beforeEach(function () { + bid = { + bidder: 'mile', + adUnitCode: 'test-ad-unit', + requestId: 'bid123', + cpm: 1.5, + width: 300, + height: 250, + nurl: 'https://example.com/win', + meta: { + upstreamBidder: 'upstreamBidder', + siteUID: 'mRUDIL', + publisherID: 'pub456', + page: 'https://example.com/page', + domain: 'example.com' + } + }; + + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should call ajax with win notification endpoint', function () { + spec.onBidWon(bid); + + expect(ajaxStub.calledTwice).to.be.true; + + // First call to notification endpoint + const firstCall = ajaxStub.getCall(0); + expect(firstCall.args[0]).to.equal('https://e01.atmtd.com/bidanalytics-event/json'); + + // Second call to nurl + const secondCall = ajaxStub.getCall(1); + expect(secondCall.args[0]).to.equal('https://example.com/win'); + }); + + it('should send correct win notification data', function () { + spec.onBidWon(bid); + + const firstCall = ajaxStub.getCall(0); + const notificationData = JSON.parse(firstCall.args[2])[0]; + + expect(notificationData.adUnitCode).to.equal('test-ad-unit'); + expect(notificationData.metaData.impressionID[0]).to.equal('bid123'); + expect(notificationData.winningBidder).to.equal('upstreamBidder'); + expect(notificationData.cpm).to.equal(1.5); + expect(notificationData.winningSize).to.equal('300x250'); + expect(notificationData.eventType).to.equal('mile-bidder-win-notify'); + expect(notificationData.timestamp).to.be.a('number'); + expect(notificationData.siteUID).to.equal('mRUDIL'); + expect(notificationData.yetiPublisherID).to.equal('pub456'); + expect(notificationData.page).to.equal('https://example.com/page'); + expect(notificationData.site).to.equal('example.com'); + }); + + it('should call nurl with GET request', function () { + spec.onBidWon(bid); + + const secondCall = ajaxStub.getCall(1); + const options = secondCall.args[3]; + + expect(options.method).to.equal('GET'); + }); + }); + + describe('onTimeout', function () { + let timeoutData, ajaxStub; + + beforeEach(function () { + timeoutData = [ + { + bidder: 'mile', + bidId: 'bid123', + adUnitCode: 'test-ad-unit-1', + timeout: 3000, + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + } + }, + { + bidder: 'mile', + bidId: 'bid456', + adUnitCode: 'test-ad-unit-2', + timeout: 3000, + params: { + placementId: '67890', + siteId: 'site123', + publisherId: 'pub456' + } + } + ]; + + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should call ajax for each timed out bid', function () { + spec.onTimeout(timeoutData); + + expect(ajaxStub.callCount).to.equal(1); + }); + + it('should send correct timeout notification data', function () { + spec.onTimeout(timeoutData); + + const firstCall = ajaxStub.getCall(0); + expect(firstCall.args[0]).to.equal('https://e01.atmtd.com/bidanalytics-event/json'); + + const notificationData = JSON.parse(firstCall.args[2])[0]; + expect(notificationData.adUnitCode).to.equal('test-ad-unit-1'); + expect(notificationData.metaData.impressionID[0]).to.equal('bid123'); + expect(notificationData.metaData.configuredTimeout[0]).to.equal('3000'); + expect(notificationData.eventType).to.equal('mile-bidder-timeout'); + expect(notificationData.timestamp).to.be.a('number'); + }); + + it('should handle single timeout', function () { + spec.onTimeout([timeoutData[0]]); + + expect(ajaxStub.calledOnce).to.be.true; + }); + + it('should handle empty timeout array', function () { + spec.onTimeout([]); + + expect(ajaxStub.called).to.be.false; + }); + }); + + describe('adapter specification', function () { + it('should have correct bidder code', function () { + expect(spec.code).to.equal('mile'); + }); + + it('should support BANNER media type', function () { + expect(spec.supportedMediaTypes).to.be.an('array'); + expect(spec.supportedMediaTypes).to.include(BANNER); + }); + + it('should have required adapter functions', function () { + expect(spec.isBidRequestValid).to.be.a('function'); + expect(spec.buildRequests).to.be.a('function'); + expect(spec.interpretResponse).to.be.a('function'); + expect(spec.getUserSyncs).to.be.a('function'); + expect(spec.onBidWon).to.be.a('function'); + expect(spec.onTimeout).to.be.a('function'); + }); + }); +}); diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index f4e09a981fe..c52f738ca55 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -367,5 +367,69 @@ describe('Missena Adapter', function () { expect(userSync[0].type).to.be.equal('iframe'); expect(userSync[0].url).to.be.equal(expectedUrl); }); + + it('sync frame url should contain gpp data when present', function () { + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7, 8], + }; + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + {}, + undefined, + gppConsent, + ); + expect(userSync.length).to.be.equal(1); + expect(userSync[0].type).to.be.equal('iframe'); + const syncUrl = new URL(userSync[0].url); + expect(syncUrl.searchParams.get('gpp')).to.equal(gppConsent.gppString); + expect(syncUrl.searchParams.get('gpp_sid')).to.equal('7,8'); + }); + + it('sync frame url should not contain gpp data when gppConsent is undefined', function () { + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + {}, + undefined, + undefined, + ); + expect(userSync.length).to.be.equal(1); + expect(userSync[0].url).to.not.contain('gpp'); + }); + + it('sync frame url should not contain gpp data when gppString is empty', function () { + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + {}, + undefined, + { gppString: '', applicableSections: [7] }, + ); + expect(userSync.length).to.be.equal(1); + expect(userSync[0].url).to.not.contain('gpp'); + }); + + it('sync frame url should contain all consent params together', function () { + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7], + }; + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + { gdprApplies: true, consentString }, + '1YNN', + gppConsent, + ); + expect(userSync.length).to.be.equal(1); + const syncUrl = new URL(userSync[0].url); + expect(syncUrl.searchParams.get('gdpr')).to.equal('1'); + expect(syncUrl.searchParams.get('gdpr_consent')).to.equal(consentString); + expect(syncUrl.searchParams.get('us_privacy')).to.equal('1YNN'); + expect(syncUrl.searchParams.get('gpp')).to.equal(gppConsent.gppString); + expect(syncUrl.searchParams.get('gpp_sid')).to.equal('7'); + }); }); }); diff --git a/test/spec/modules/mycodemediaBidAdapter_spec.js b/test/spec/modules/mycodemediaBidAdapter_spec.js new file mode 100644 index 00000000000..fbf40f4b403 --- /dev/null +++ b/test/spec/modules/mycodemediaBidAdapter_spec.js @@ -0,0 +1,511 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/mycodemediaBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'mycodemedia'; + +describe('MyCodeMediaBidAdapter', 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 () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + 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 + } + ]; + + const 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); + const 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); + const 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] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const 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]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + 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 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const 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 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const 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 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const 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' + }] + }; + + const 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' + }] + }; + const 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', + }] + }; + const 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' + }] + }; + const 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, + }, undefined); + 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://usersync.mycodemedia.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({}, {}, {}, '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://usersync.mycodemedia.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({}, {}, {}, undefined, { + 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://usersync.mycodemedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index 4b2951afe33..bb64b283535 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -1,10 +1,12 @@ import * as neuwo from "modules/neuwoRtdProvider"; +import * as refererDetection from "src/refererDetection.js"; import { server } from "test/mocks/xhr.js"; -const NEUWO_API_URL = "https://api.url.neuwo.ai/edge/GetAiTopics"; +const NEUWO_API_URL = "https://edge.neuwo.ai/api/aitopics/edge/v1/iab"; const NEUWO_API_TOKEN = "token"; -const IAB_CONTENT_TAXONOMY_VERSION = "3.0"; +const IAB_CONTENT_TAXONOMY_VERSION = "2.2"; +// API config const config = () => ({ params: { neuwoApiUrl: NEUWO_API_URL, @@ -13,7 +15,115 @@ const config = () => ({ }, }); +/** + * API Response Mock + * Returns new format with segtax-based structure + * Field names: id, name, relevance (lowercase) + * Structure: { "6": { "1": [...], "2": [...] }, "4": { "3": [...] } } + */ function getNeuwoApiResponse() { + return { + 7: { + 1: [ + { + id: "80DV8O", + }, + { + id: "52", + }, + { + id: "432", + }, + ], + 2: [ + { + id: "90", + }, + ], + 3: [ + { + id: "106", + }, + ], + }, + 1: { + 1: [ + { + id: "IAB12", + }, + ], + }, + 6: { + 1: [ + { + id: "52", + }, + ], + 2: [ + { + id: "90", + }, + { + id: "434", + }, + ], + 3: [ + { + id: "106", + }, + ], + }, + 4: { + 3: [ + { + id: "49", + }, + { + id: "780", + }, + ], + 4: [ + { + id: "431", + }, + { + id: "196", + }, + { + id: "197", + }, + ], + 5: [ + { + id: "98", + }, + ], + }, + }; +} + +// ============================================================================ +// V1 API Constants and Mocks +// ============================================================================ + +const NEUWO_API_URL_V1 = "https://api.url.neuwo.ai/edge/GetAiTopics"; +const IAB_CONTENT_TAXONOMY_VERSION_V1 = "3.0"; + +// Legacy V1 API config (for backward compatibility tests) +const configV1 = () => ({ + params: { + neuwoApiUrl: NEUWO_API_URL_V1, + neuwoApiToken: NEUWO_API_TOKEN, + iabContentTaxonomyVersion: IAB_CONTENT_TAXONOMY_VERSION_V1, + }, +}); + +/** + * V1 API Response Mock + * Returns legacy format with marketing_categories structure + * Field names: ID, label, relevance (capital letters) + */ +function getNeuwoApiResponseV1() { return { brand_safety: { BS_score: "1.0", @@ -65,8 +175,6 @@ function getNeuwoApiResponse() { ], }; } -const CONTENT_TIERS = ["iab_tier_1", "iab_tier_2", "iab_tier_3"]; -const AUDIENCE_TIERS = ["iab_audience_tier_3", "iab_audience_tier_4", "iab_audience_tier_5"]; /** * Object generator, like above, written using alternative techniques @@ -126,38 +234,20 @@ describe("neuwoRtdModule", function () { }); describe("buildIabData", function () { - it("should return an empty segment array when no matching tiers are found", function () { - const marketingCategories = getNeuwoApiResponse().marketing_categories; - const tiers = ["non_existent_tier"]; - const segtax = 0; - const result = neuwo.buildIabData(marketingCategories, tiers, segtax); - const expected = { - name: neuwo.DATA_PROVIDER, - segment: [], - ext: { - segtax, - }, - }; - expect(result, "should produce a valid object with an empty segment array").to.deep.equal( - expected - ); - }); - it("should correctly build the data object for content tiers", function () { - const marketingCategories = getNeuwoApiResponse().marketing_categories; + // format with tier keys "1", "2", "3" + const tierData = { + "1": [{ id: "274", name: "Home & Garden", relevance: "0.47" }], + "2": [{ id: "216", name: "Cooking", relevance: "0.41" }], + "3": [], + }; const segtax = 0; - const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const result = neuwo.buildIabData(tierData, segtax); const expected = { name: neuwo.DATA_PROVIDER, segment: [ - { - id: "274", - name: "Home & Garden", - }, - { - id: "216", - name: "Cooking", - }, + { id: "274" }, + { id: "216" }, ], ext: { segtax, @@ -169,24 +259,20 @@ describe("neuwoRtdModule", function () { }); it("should correctly build the data object for audience tiers", function () { - const marketingCategories = getNeuwoApiResponse().marketing_categories; + // format with tier keys "3", "4", "5" for audience + const tierData = { + "3": [{ id: "49", name: "Demographic | Gender | Female |", relevance: "0.9923" }], + "4": [{ id: "127", name: "Demographic | Household Data | 1 Child |", relevance: "0.9673" }], + "5": [{ id: "98", name: "Demographic | Household Data | Parents with Children |", relevance: "0.9066" }], + }; const segtax = 0; - const result = neuwo.buildIabData(marketingCategories, AUDIENCE_TIERS, segtax); + const result = neuwo.buildIabData(tierData, segtax); const expected = { name: neuwo.DATA_PROVIDER, segment: [ - { - id: "49", - name: "Demographic | Gender | Female |", - }, - { - id: "127", - name: "Demographic | Household Data | 1 Child |", - }, - { - id: "98", - name: "Demographic | Household Data | Parents with Children |", - }, + { id: "49" }, + { id: "127" }, + { id: "98" }, ], ext: { segtax, @@ -197,7 +283,7 @@ describe("neuwoRtdModule", function () { ); }); - it("should return an empty segment array when marketingCategories is null or undefined", function () { + it("should return an empty segment array when tierData is null or undefined", function () { const segtax = 4; const expected = { name: neuwo.DATA_PROVIDER, @@ -207,19 +293,19 @@ describe("neuwoRtdModule", function () { }, }; expect( - neuwo.buildIabData(null, CONTENT_TIERS, segtax), - "should handle null marketingCategories gracefully" + neuwo.buildIabData(null, segtax), + "should handle null tierData gracefully" ).to.deep.equal(expected); expect( - neuwo.buildIabData(undefined, CONTENT_TIERS, segtax), - "should handle undefined marketingCategories gracefully" + neuwo.buildIabData(undefined, segtax), + "should handle undefined tierData gracefully" ).to.deep.equal(expected); }); - it("should return an empty segment array when marketingCategories is empty", function () { - const marketingCategories = {}; + it("should return an empty segment array when tierData is empty", function () { + const tierData = {}; const segtax = 4; - const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const result = neuwo.buildIabData(tierData, segtax); const expected = { name: neuwo.DATA_PROVIDER, segment: [], @@ -227,23 +313,23 @@ describe("neuwoRtdModule", function () { segtax, }, }; - expect(result, "should handle an empty marketingCategories object").to.deep.equal(expected); + expect(result, "should handle an empty tierData object").to.deep.equal(expected); }); - it("should gracefully handle if a marketing_categories key contains a non-array value", function () { - const marketingCategories = getNeuwoApiResponse().marketing_categories; - // Overwrite iab_tier_1 to be an object instead of an array - marketingCategories.iab_tier_1 = { ID: "274", label: "Home & Garden" }; + it("should gracefully handle if a tier key contains a non-array value", function () { + const tierData = { + "1": { id: "274", name: "Home & Garden" }, // Not an array + "2": [{ id: "216", name: "Cooking", relevance: "0.41" }], + }; const segtax = 4; - const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const result = neuwo.buildIabData(tierData, segtax); const expected = { name: neuwo.DATA_PROVIDER, - // The segment should only contain data from the valid iab_tier_2 + // The segment should only contain data from the valid tier "2" segment: [ { id: "216", - name: "Cooking", }, ], ext: { @@ -257,30 +343,29 @@ describe("neuwoRtdModule", function () { }); it("should ignore malformed objects within a tier array", function () { - const marketingCategories = getNeuwoApiResponse().marketing_categories; - // Overwrite iab_tier_1 with various malformed objects - marketingCategories.iab_tier_1 = [ - { ID: "274", label: "Valid Object" }, - { ID: "999" }, // Missing 'label' property - { label: "Another Label" }, // Missing 'ID' property - null, // A null value - "just-a-string", // A string primitive - {}, // An empty object - ]; + // Tier "1" with various malformed objects + const tierData = { + "1": [ + { id: "274", name: "Valid Object" }, + { name: "Another Label" }, // Missing 'id' property + null, // A null value + "just-a-string", // A string primitive + {}, // An empty object + ], + "2": [{ id: "216", name: "Cooking", relevance: "0.41" }], + }; const segtax = 4; - const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const result = neuwo.buildIabData(tierData, segtax); const expected = { name: neuwo.DATA_PROVIDER, - // The segment should contain the one valid object from iab_tier_1 and the data from iab_tier_2 + // The segment should contain the one valid object from tier "1" and the data from tier "2" segment: [ { id: "274", - name: "Valid Object", }, { id: "216", - name: "Cooking", }, ], ext: { @@ -293,7 +378,7 @@ describe("neuwoRtdModule", function () { ); }); - it("should return an empty segment array if the entire marketingCategories object is not a valid object", function () { + it("should return an empty segment array if the entire tierData is not a valid object", function () { const segtax = 4; const expected = { name: neuwo.DATA_PROVIDER, @@ -301,1000 +386,3636 @@ describe("neuwoRtdModule", function () { ext: { segtax }, }; // Test with a string - const resultString = neuwo.buildIabData("incorrect format", CONTENT_TIERS, segtax); - expect(resultString, "should handle non-object marketingCategories input").to.deep.equal( + const resultString = neuwo.buildIabData("incorrect format", segtax); + expect(resultString, "should handle non-object tierData input").to.deep.equal( expected ); }); }); - describe("injectOrtbData", function () { - it("should correctly mutate the request bids config object with new data", function () { - const reqBidsConfigObj = { ortb2Fragments: { global: {} } }; - neuwo.injectOrtbData(reqBidsConfigObj, "c.d.e.f", { g: "h" }); - expect( - reqBidsConfigObj.ortb2Fragments.global.c.d.e.f.g, - "should deeply merge the new data into the target object" - ).to.equal("h"); + describe("buildFilterQueryParams", function () { + it("should return empty array when no filters provided", function () { + const result = neuwo.buildFilterQueryParams(null, 6); + expect(result, "should return empty array for null filters").to.deep.equal([]); }); - }); - - describe("getBidRequestData", function () { - describe("when using IAB Content Taxonomy 3.0", function () { - it("should correctly structure the bids object after a successful API response", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig = bidsConfiglike(); - const conf = config(); - // control xhr api request target for testing - conf.params.websiteToAnalyseUrl = - "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + it("should return empty array when filters parameter is undefined", function () { + const result = neuwo.buildFilterQueryParams(undefined, 6); + expect(result, "should return empty array for undefined filters").to.deep.equal([]); + }); - expect(request.url, "The request URL should be a string").to.be.a("string"); - expect(request.url, "The request URL should include the public API token").to.include( - conf.params.neuwoApiToken - ); - expect(request.url, "The request URL should include the encoded website URL").to.include( - encodeURIComponent(conf.params.websiteToAnalyseUrl) - ); + it("should return empty array when filters is empty object", function () { + const result = neuwo.buildFilterQueryParams({}, 6); + expect(result, "should return empty array for empty filters").to.deep.equal([]); + }); - request.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + it("should convert ContentTier1 filter correctly", function () { + const filters = { + ContentTier1: { limit: 3, threshold: 0.5 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; - expect(contentData.name, "The data provider name should be correctly set").to.equal( - neuwo.DATA_PROVIDER - ); - expect( - contentData.ext.segtax, - "The segtax value should correspond to IAB Content Taxonomy 3.0" - ).to.equal(7); - expect( - contentData.segment[0].id, - "The first segment ID should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); - expect( - contentData.segment[1].name, - "The second segment name should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].label); - }); + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.have.lengthOf(2); }); - describe("when using IAB Content Taxonomy 2.2", function () { - it("should correctly structure the bids object after a successful API response", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.iabContentTaxonomyVersion = "2.2"; - // control xhr api request target for testing - conf.params.websiteToAnalyseUrl = - "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + it("should convert ContentTier2 filter correctly", function () { + const filters = { + ContentTier2: { limit: 5, threshold: 0.6 } + }; + const contentSegtax = 7; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + expect(result).to.include("filter_7_2_limit=5"); + expect(result).to.include("filter_7_2_threshold=0.6"); + expect(result).to.have.lengthOf(2); + }); - expect(request.url, "The request URL should be a string").to.be.a("string"); - expect(request.url, "The request URL should include the public API token").to.include( - conf.params.neuwoApiToken - ); - expect(request.url, "The request URL should include the encoded website URL").to.include( - encodeURIComponent(conf.params.websiteToAnalyseUrl) - ); + it("should convert ContentTier3 filter correctly", function () { + const filters = { + ContentTier3: { limit: 4, threshold: 0.8 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - request.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); - const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; - expect(contentData.name, "The data provider name should be correctly set").to.equal( - neuwo.DATA_PROVIDER - ); - expect( - contentData.ext.segtax, - "The segtax value should correspond to IAB Content Taxonomy 2.2" - ).to.equal(6); - expect( - contentData.segment[0].id, - "The first segment ID should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); - expect( - contentData.segment[1].name, - "The second segment name should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].label); - }); + expect(result).to.include("filter_6_3_limit=4"); + expect(result).to.include("filter_6_3_threshold=0.8"); + expect(result).to.have.lengthOf(2); }); - describe("when using the default IAB Content Taxonomy", function () { - it("should correctly structure the bids object after a successful API response", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.iabContentTaxonomyVersion = undefined; - // control xhr api request target for testing - conf.params.websiteToAnalyseUrl = - "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + it("should convert AudienceTier3 filter correctly", function () { + const filters = { + AudienceTier3: { limit: 2, threshold: 0.9 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.include("filter_4_3_threshold=0.9"); + expect(result).to.have.lengthOf(2); + }); - expect(request.url, "The request URL should be a string").to.be.a("string"); - expect(request.url, "The request URL should include the public API token").to.include( - conf.params.neuwoApiToken - ); - expect(request.url, "The request URL should include the encoded website URL").to.include( - encodeURIComponent(conf.params.websiteToAnalyseUrl) - ); + it("should convert AudienceTier4 filter correctly", function () { + const filters = { + AudienceTier4: { limit: 10, threshold: 0.85 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - request.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); - const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; - expect(contentData.name, "The data provider name should be correctly set").to.equal( - neuwo.DATA_PROVIDER - ); - expect( - contentData.ext.segtax, - "The segtax value should default to IAB Content Taxonomy 3.0" - ).to.equal(7); - expect( - contentData.segment[0].id, - "The first segment ID should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); - expect( - contentData.segment[1].name, - "The second segment name should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].label); - }); + expect(result).to.include("filter_4_4_limit=10"); + expect(result).to.include("filter_4_4_threshold=0.85"); + expect(result).to.have.lengthOf(2); }); - describe("when using IAB Audience Taxonomy 1.1", function () { - it("should correctly structure the bids object after a successful API response", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig = bidsConfiglike(); - const conf = config(); - // control xhr api request target for testing - conf.params.websiteToAnalyseUrl = - "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + it("should convert AudienceTier5 filter correctly", function () { + const filters = { + AudienceTier5: { limit: 7, threshold: 0.95 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + expect(result).to.include("filter_4_5_limit=7"); + expect(result).to.include("filter_4_5_threshold=0.95"); + expect(result).to.have.lengthOf(2); + }); - expect(request.url, "The request URL should be a string").to.be.a("string"); - expect(request.url, "The request URL should include the public API token").to.include( - conf.params.neuwoApiToken - ); - expect(request.url, "The request URL should include the encoded website URL").to.include( - encodeURIComponent(conf.params.websiteToAnalyseUrl) - ); + it("should handle multiple content tiers with same segtax", function () { + const filters = { + ContentTier1: { limit: 3 }, + ContentTier2: { limit: 5 }, + ContentTier3: { threshold: 0.7 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); - request.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); - const userData = bidsConfig.ortb2Fragments.global.user.data[0]; - expect(userData.name, "The data provider name should be correctly set").to.equal( - neuwo.DATA_PROVIDER - ); - expect( - userData.ext.segtax, - "The segtax value should correspond to IAB Audience Taxonomy 1.1" - ).to.equal(4); - expect( - userData.segment[0].id, - "The first segment ID should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_audience_tier_3[0].ID); - expect( - userData.segment[1].name, - "The second segment name should match the API response" - ).to.equal(apiResponse.marketing_categories.iab_audience_tier_4[0].label); - }); + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.include("filter_6_3_threshold=0.7"); + expect(result).to.have.lengthOf(3); }); - it("should not change the bids object structure after an unsuccessful API response", function () { - const bidsConfig = bidsConfiglike(); - const bidsConfigCopy = bidsConfiglike(); - const conf = config(); + it("should handle multiple audience tiers", function () { + const filters = { + AudienceTier3: { limit: 2 }, + AudienceTier4: { limit: 4 }, + AudienceTier5: { threshold: 0.85 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; - request.respond( - 404, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify({ detail: "test error" }) - ); - expect( - bidsConfig, - "The bids config object should remain unmodified after a failed API call" - ).to.deep.equal(bidsConfigCopy); + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.include("filter_4_4_limit=4"); + expect(result).to.include("filter_4_5_threshold=0.85"); + expect(result).to.have.lengthOf(3); }); - }); - - describe("cleanUrl", function () { - describe("when no stripping options are provided", function () { - it("should return the URL unchanged", function () { - const url = "https://example.com/page?foo=bar&baz=qux"; - const result = neuwo.cleanUrl(url, {}); - expect(result, "should return the original URL with all query params intact").to.equal(url); - }); - it("should return the URL unchanged when options object is empty", function () { - const url = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url); - expect(result, "should handle missing options parameter").to.equal(url); - }); + it("should handle both content and audience tiers together", function () { + const filters = { + ContentTier1: { limit: 3, threshold: 0.5 }, + ContentTier2: { limit: 5 }, + AudienceTier3: { limit: 2, threshold: 0.9 }, + AudienceTier4: { threshold: 0.8 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.include("filter_4_3_threshold=0.9"); + expect(result).to.include("filter_4_4_threshold=0.8"); + expect(result).to.have.lengthOf(6); }); - describe("with query parameters edge cases", function () { - it("should strip all query parameters from the URL for `stripAllQueryParams` (edge cases)", function () { - const stripAll = (url) => neuwo.cleanUrl(url, { stripAllQueryParams: true }); - const expected = "https://example.com/page"; - const expectedWithFragment = "https://example.com/page#anchor"; - - // Basic formats - expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); - expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); - expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); - expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); - - // Multiple parameters - expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); - expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + it("should use different content segtax values correctly", function () { + const filters = { + ContentTier1: { limit: 3 } + }; - // Special characters and encoding - expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); - expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); - expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); - expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); - expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + // Test with segtax 6 (IAB 2.2) + const result6 = neuwo.buildFilterQueryParams(filters, 6, false); + expect(result6).to.include("filter_6_1_limit=3"); + expect(result6).to.not.include("filter_7_1_limit=3"); - // Delimiters and syntax edge cases - expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); - expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + // Test with segtax 7 (IAB 3.0) + const result7 = neuwo.buildFilterQueryParams(filters, 7, false); + expect(result7).to.include("filter_7_1_limit=3"); + expect(result7).to.not.include("filter_6_1_limit=3"); + }); - // Empty and missing cases - expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); - expect(stripAll("https://example.com/page??"), "should remove double question mark").to.equal(expected); - expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + it("should ignore unknown tier names", function () { + const filters = { + ContentTier1: { limit: 3 }, + UnknownTier: { limit: 10 }, + InvalidTier99: { threshold: 0.5 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Unicode and special values - expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); - expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); - expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.have.lengthOf(1); + }); - // Fragment positioning (fragments are preserved by default) - expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); - expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); - }); + it("should handle filters with only limit property", function () { + const filters = { + ContentTier1: { limit: 5 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - it("should strip all query parameters from the URL for `stripQueryParamsForDomains` (edge cases)", function () { - const stripAll = (url) => neuwo.cleanUrl(url, { stripQueryParamsForDomains: ["example.com"] }); - const expected = "https://example.com/page"; - const expectedWithFragment = "https://example.com/page#anchor"; + expect(result).to.include("filter_6_1_limit=5"); + expect(result).to.not.include.match(/filter_6_1_threshold/); + expect(result).to.have.lengthOf(1); + }); - // Basic formats - expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); - expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); - expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); - expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + it("should handle filters with only threshold property", function () { + const filters = { + AudienceTier3: { threshold: 0.75 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Multiple parameters - expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); - expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + expect(result).to.include("filter_4_3_threshold=0.75"); + expect(result).to.not.include.match(/filter_4_3_limit/); + expect(result).to.have.lengthOf(1); + }); - // Special characters and encoding - expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); - expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); - expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); - expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); - expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + it("should handle filters with additional custom properties", function () { + const filters = { + ContentTier1: { limit: 3, threshold: 0.5, customProp: "value" } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Delimiters and syntax edge cases - expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); - expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.include("filter_6_1_customProp=value"); + expect(result).to.have.lengthOf(3); + }); - // Empty and missing cases - expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); - expect(stripAll("https://example.com/page??"), "should remove double question mark").to.equal(expected); - expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + it("should handle empty filter objects for tiers", function () { + const filters = { + ContentTier1: {}, + AudienceTier3: {} + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Unicode and special values - expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); - expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); - expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + expect(result).to.have.lengthOf(0); + }); - // Fragment positioning (fragments are preserved by default) - expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); - expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); - }); + it("should not include null limit value", function () { + const filters = { + ContentTier1: { limit: null, threshold: 0.5 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - it("should strip all query parameters from the URL for `stripQueryParams` (edge cases)", function () { - const stripAll = (url) => neuwo.cleanUrl(url, { stripQueryParams: ["key", "key1", "key2", "", "?"] }); - const expected = "https://example.com/page"; - const expectedWithFragment = "https://example.com/page#anchor"; + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.have.lengthOf(1); + }); - // Basic formats - expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); - expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); - expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); - expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + it("should not include undefined limit value", function () { + const filters = { + ContentTier1: { limit: undefined, threshold: 0.5 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Multiple parameters - expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); - expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.have.lengthOf(1); + }); - // Special characters and encoding - expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); - expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); - expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); - expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); - expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + it("should not include null threshold value", function () { + const filters = { + AudienceTier3: { limit: 5, threshold: null } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Delimiters and syntax edge cases - expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); - expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); - expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + expect(result).to.include("filter_4_3_limit=5"); + expect(result).to.have.lengthOf(1); + }); - // Empty and missing cases - expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); - expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + it("should not include undefined threshold value", function () { + const filters = { + AudienceTier3: { limit: 5, threshold: undefined } + }; + const result = neuwo.buildFilterQueryParams(filters, 6, false); - // Unicode and special values - expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); - expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); - expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + expect(result).to.include("filter_4_3_limit=5"); + expect(result).to.have.lengthOf(1); + }); - // Fragment positioning (fragments are preserved by default) - expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); - expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + // OpenRTB 2.5 Feature Tests + describe("with enableOrtb25Fields enabled (default)", function () { + it("should add IAB 1.0 filter params when ContentTier1 filter is provided", function () { + const filters = { + ContentTier1: { limit: 3, threshold: 0.5 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.include("filter_1_1_limit=3"); + expect(result).to.include("filter_1_1_threshold=0.5"); + expect(result).to.have.lengthOf(4); }); - }); - describe("when stripAllQueryParams is true", function () { - it("should strip all query parameters from the URL", function () { - const url = "https://example.com/page?foo=bar&baz=qux&test=123"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); - expect(result, "should remove all query parameters").to.equal(expected); + it("should add IAB 1.0 filter params when ContentTier2 filter is provided", function () { + const filters = { + ContentTier2: { limit: 5, threshold: 0.6 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax); + + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.include("filter_6_2_threshold=0.6"); + expect(result).to.include("filter_1_2_limit=5"); + expect(result).to.include("filter_1_2_threshold=0.6"); + expect(result).to.have.lengthOf(4); }); - it("should return the URL unchanged if there are no query parameters", function () { - const url = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); - expect(result, "should handle URLs without query params").to.equal(url); + it("should add IAB 1.0 filter params for both ContentTier1 and ContentTier2", function () { + const filters = { + ContentTier1: { limit: 3 }, + ContentTier2: { limit: 5 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.include("filter_1_1_limit=3"); + expect(result).to.include("filter_1_2_limit=5"); + expect(result).to.have.lengthOf(4); }); - it("should preserve the hash fragment when stripping query params without stripFragments", function () { - const url = "https://example.com/page?foo=bar#section"; - const expected = "https://example.com/page#section"; - const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); - expect(result, "should preserve hash fragments by default").to.equal(expected); + it("should NOT add ContentTier3 filter to IAB 1.0 (only tiers 1-2 exist)", function () { + const filters = { + ContentTier1: { limit: 3 }, + ContentTier2: { limit: 5 }, + ContentTier3: { threshold: 0.7 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.include("filter_6_3_threshold=0.7"); + expect(result).to.include("filter_1_1_limit=3"); + expect(result).to.include("filter_1_2_limit=5"); + expect(result).to.not.include.match(/filter_1_3/); + expect(result).to.have.lengthOf(5); }); - it("should strip hash fragment when stripFragments is enabled", function () { - const url = "https://example.com/page?foo=bar#section"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { stripAllQueryParams: true, stripFragments: true }); - expect(result, "should strip both query params and fragments").to.equal(expected); + it("should NOT add audience tier filters to IAB 1.0", function () { + const filters = { + AudienceTier3: { limit: 2 }, + AudienceTier4: { limit: 4 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6); + + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.include("filter_4_4_limit=4"); + expect(result).to.not.include.match(/filter_1/); + expect(result).to.have.lengthOf(2); }); - it("should strip query params but preserve path and protocol", function () { - const url = "https://subdomain.example.com:8080/path/to/page?param=value"; - const expected = "https://subdomain.example.com:8080/path/to/page"; - const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); - expect(result, "should preserve protocol, domain, port, and path").to.equal(expected); + it("should add IAB 1.0 filter params alongside audience filters", function () { + const filters = { + ContentTier1: { limit: 3 }, + AudienceTier3: { limit: 2 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_1_1_limit=3"); + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.have.lengthOf(3); }); - }); - describe("when stripQueryParamsForDomains is provided", function () { - it("should strip all query params for exact domain match", function () { - const url = "https://example.com/page?foo=bar&baz=qux"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"] - }); - expect(result, "should strip params for exact domain match").to.equal(expected); + it("should not add segtax 1 params when no content filters are provided", function () { + const filters = { + AudienceTier3: { limit: 2 } + }; + const result = neuwo.buildFilterQueryParams(filters, 6); + + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.not.include.match(/filter_1/); + expect(result).to.have.lengthOf(1); }); - it("should strip all query params for subdomain match", function () { - const url = "https://sub.example.com/page?foo=bar"; - const expected = "https://sub.example.com/page"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"] - }); - expect(result, "should strip params for subdomains").to.equal(expected); + it("should not add segtax 1 params when filters is empty", function () { + const result = neuwo.buildFilterQueryParams({}, 6); + + expect(result).to.deep.equal([]); }); - it("should not strip query params if domain does not match", function () { - const url = "https://other.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"] - }); - expect(result, "should preserve params for non-matching domains").to.equal(url); + it("should not add segtax 1 params when filters is null", function () { + const result = neuwo.buildFilterQueryParams(null, 6); + + expect(result).to.deep.equal([]); }); - it("should not strip query params if subdomain is provided for domain", function () { - const url = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["sub.example.com"] - }); - expect(result, "should preserve params for domain when subdomain is provided").to.equal(url); + it("should work with different content segtax values", function () { + const filters = { + ContentTier1: { limit: 3 } + }; + + // Test with segtax 7 (IAB 3.0) + const result7 = neuwo.buildFilterQueryParams(filters, 7); + expect(result7).to.include("filter_7_1_limit=3"); + expect(result7).to.include("filter_1_1_limit=3"); + expect(result7).to.have.lengthOf(2); }); - it("should handle multiple domains in the list", function () { - const url1 = "https://example.com/page?foo=bar"; - const url2 = "https://test.com/page?foo=bar"; - const url3 = "https://other.com/page?foo=bar"; - const domains = ["example.com", "test.com"]; + it("should not produce duplicate query params when contentSegtax is 1", function () { + const filters = { + ContentTier1: { limit: 5, threshold: 0.8 }, + ContentTier2: { limit: 3 } + }; - const result1 = neuwo.cleanUrl(url1, { stripQueryParamsForDomains: domains }); - const result2 = neuwo.cleanUrl(url2, { stripQueryParamsForDomains: domains }); - const result3 = neuwo.cleanUrl(url3, { stripQueryParamsForDomains: domains }); + const result = neuwo.buildFilterQueryParams(filters, 1); - expect(result1, "should strip params for first domain").to.equal("https://example.com/page"); - expect(result2, "should strip params for second domain").to.equal("https://test.com/page"); - expect(result3, "should preserve params for non-listed domain").to.equal(url3); + // Count occurrences of each param to verify no duplicates + const countOccurrences = (arr, val) => arr.filter(p => p === val).length; + expect(countOccurrences(result, "filter_1_1_limit=5"), "filter_1_1_limit should appear once").to.equal(1); + expect(countOccurrences(result, "filter_1_1_threshold=0.8"), "filter_1_1_threshold should appear once").to.equal(1); + expect(countOccurrences(result, "filter_1_2_limit=3"), "filter_1_2_limit should appear once").to.equal(1); + expect(result, "should have exactly 3 params total").to.have.lengthOf(3); }); + }); - it("should handle deep subdomains correctly", function () { - const url = "https://deep.sub.example.com/page?foo=bar"; - const expected = "https://deep.sub.example.com/page"; - const result1 = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"] - }); - const result2 = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["sub.example.com"] - }); - expect(result1, "should strip params for deep subdomains with domain matching").to.equal(expected); - expect(result2, "should strip params for deep subdomains with subdomain matching").to.equal(expected); + describe("with enableOrtb25Fields disabled", function () { + it("should not add IAB 1.0 filter params when disabled", function () { + const filters = { + ContentTier1: { limit: 3, threshold: 0.5 }, + ContentTier2: { limit: 5 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_1_threshold=0.5"); + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.not.include.match(/filter_1/); + expect(result).to.have.lengthOf(3); }); - it("should not match partial domain names", function () { - const url = "https://notexample.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"] - }); - expect(result, "should not match partial domain strings").to.equal(url); + it("should work correctly with all tier types when disabled", function () { + const filters = { + ContentTier1: { limit: 3 }, + ContentTier2: { limit: 5 }, + ContentTier3: { threshold: 0.7 }, + AudienceTier3: { limit: 2 } + }; + const contentSegtax = 6; + const result = neuwo.buildFilterQueryParams(filters, contentSegtax, false); + + expect(result).to.include("filter_6_1_limit=3"); + expect(result).to.include("filter_6_2_limit=5"); + expect(result).to.include("filter_6_3_threshold=0.7"); + expect(result).to.include("filter_4_3_limit=2"); + expect(result).to.not.include.match(/filter_1/); + expect(result).to.have.lengthOf(4); }); + }); + }); - it("should handle empty domain list", function () { - const url = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { stripQueryParamsForDomains: [] }); - expect(result, "should not strip params with empty domain list").to.equal(url); - }); + describe("extractCategoryIds", function () { + it("should extract IDs from single tier", function () { + const tierData = { + "1": [ + { id: "IAB12" }, + { id: "IAB12-3" } + ] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should extract all IDs from tier 1").to.deep.equal(["IAB12", "IAB12-3"]); }); - describe("when stripQueryParams is provided", function () { - it("should strip only specified query parameters", function () { - const url = "https://example.com/page?foo=bar&baz=qux&keep=this"; - const expected = "https://example.com/page?keep=this"; - const result = neuwo.cleanUrl(url, { - stripQueryParams: ["foo", "baz"] + it("should extract IDs from multiple tiers", function () { + const tierData = { + "1": [ + { id: "IAB12" }, + { id: "IAB12-3" } + ], + "2": [ + { id: "IAB12-5" } + ] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should extract all IDs from all tiers").to.deep.equal(["IAB12", "IAB12-3", "IAB12-5"]); + }); + + it("should handle empty tier arrays", function () { + const tierData = { + "1": [], + "2": [ + { id: "IAB12" } + ] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should only extract from non-empty tiers").to.deep.equal(["IAB12"]); + }); + + it("should skip items without id property", function () { + const tierData = { + "1": [ + { id: "IAB12" }, + { name: "No ID" }, + { id: "IAB12-3" } + ] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should skip items without id").to.deep.equal(["IAB12", "IAB12-3"]); + }); + + it("should return empty array for null tierData", function () { + const result = neuwo.extractCategoryIds(null); + expect(result, "should return empty array for null").to.deep.equal([]); + }); + + it("should return empty array for undefined tierData", function () { + const result = neuwo.extractCategoryIds(undefined); + expect(result, "should return empty array for undefined").to.deep.equal([]); + }); + + it("should return empty array for empty object", function () { + const result = neuwo.extractCategoryIds({}); + expect(result, "should return empty array for empty object").to.deep.equal([]); + }); + + it("should handle non-array tier values", function () { + const tierData = { + "1": { id: "IAB12" }, // Not an array + "2": [ + { id: "IAB13" } + ] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should skip non-array values").to.deep.equal(["IAB13"]); + }); + + it("should handle null items in tier arrays", function () { + const tierData = { + "1": [ + { id: "IAB12" }, + null, + { id: "IAB12-3" } + ] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should skip null items").to.deep.equal(["IAB12", "IAB12-3"]); + }); + + it("should handle non-object tierData", function () { + expect(neuwo.extractCategoryIds("string"), "should handle string").to.deep.equal([]); + expect(neuwo.extractCategoryIds(123), "should handle number").to.deep.equal([]); + expect(neuwo.extractCategoryIds([]), "should handle array").to.deep.equal([]); + }); + + it("should extract from all tier numbers", function () { + const tierData = { + "1": [{ id: "IAB1" }], + "2": [{ id: "IAB2" }], + "3": [{ id: "IAB3" }], + "4": [{ id: "IAB4" }], + "5": [{ id: "IAB5" }] + }; + const result = neuwo.extractCategoryIds(tierData); + expect(result, "should extract from all tiers").to.deep.equal(["IAB1", "IAB2", "IAB3", "IAB4", "IAB5"]); + }); + }); + + describe("injectOrtbData", function () { + it("should correctly mutate the request bids config object with new data", function () { + const reqBidsConfigObj = { ortb2Fragments: { global: {} } }; + neuwo.injectOrtbData(reqBidsConfigObj, "c.d.e.f", { g: "h" }); + expect( + reqBidsConfigObj.ortb2Fragments.global.c.d.e.f.g, + "should deeply merge the new data into the target object" + ).to.equal("h"); + }); + }); + + describe("injectIabCategories", function () { + it("should not inject data when responseParsed is null or undefined", function () { + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfigCopy = JSON.parse(JSON.stringify(bidsConfig1)); + + neuwo.injectIabCategories(null, bidsConfig1, "2.2"); + neuwo.injectIabCategories(undefined, bidsConfig2, "2.2"); + + expect( + bidsConfig1.ortb2Fragments.global, + "should not modify ortb2Fragments when response is null" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + expect( + bidsConfig2.ortb2Fragments.global, + "should not modify ortb2Fragments when response is undefined" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + }); + + it("should not inject data when responseParsed is not an object", function () { + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const bidsConfigCopy = JSON.parse(JSON.stringify(bidsConfig1)); + + neuwo.injectIabCategories("invalid string", bidsConfig1, "2.2"); + neuwo.injectIabCategories(123, bidsConfig2, "2.2"); + neuwo.injectIabCategories([1, 2, 3], bidsConfig3, "2.2"); + + expect( + bidsConfig1.ortb2Fragments.global, + "should not modify ortb2Fragments when response is a string" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + expect( + bidsConfig2.ortb2Fragments.global, + "should not modify ortb2Fragments when response is a number" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + expect( + bidsConfig3.ortb2Fragments.global, + "should not modify ortb2Fragments when response is an array" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + }); + + it("should handle empty object response", function () { + const bidsConfig = bidsConfiglike(); + const bidsConfigCopy = JSON.parse(JSON.stringify(bidsConfig)); + + neuwo.injectIabCategories({}, bidsConfig, "2.2"); + + expect( + bidsConfig.ortb2Fragments.global, + "should not inject data when response object is empty" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + }); + + it("should inject content data when valid content segments exist", function () { + const response = { + "6": { + "1": [{ id: "52", name: "Food & Drink" }], + "2": [{ id: "90", name: "Cooking" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData, "should have content data").to.exist; + expect(contentData.ext.segtax, "should have correct segtax").to.equal(6); + expect(contentData.segment, "should have segments").to.have.lengthOf(2); + expect(contentData.segment[0].id, "first segment should match").to.equal("52"); + }); + + it("should inject audience data when valid audience segments exist", function () { + const response = { + "4": { + "3": [{ id: "49", name: "Female" }], + "4": [{ id: "431", name: "Age 25-34" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const userData = bidsConfig.ortb2Fragments.global?.user?.data?.[0]; + expect(userData, "should have user data").to.exist; + expect(userData.ext.segtax, "should have correct segtax").to.equal(4); + expect(userData.segment, "should have segments").to.have.lengthOf(2); + expect(userData.segment[0].id, "first segment should match").to.equal("49"); + }); + + it("should inject both content and audience data when both exist", function () { + const response = { + "6": { + "1": [{ id: "52", name: "Food & Drink" }] + }, + "4": { + "3": [{ id: "49", name: "Female" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + const userData = bidsConfig.ortb2Fragments.global?.user?.data?.[0]; + + expect(contentData, "should have content data").to.exist; + expect(userData, "should have user data").to.exist; + expect(contentData.ext.segtax, "content should have segtax 6").to.equal(6); + expect(userData.ext.segtax, "audience should have segtax 4").to.equal(4); + }); + + it("should not inject empty audience data when only content segments exist", function () { + const response = { + "6": { + "1": [{ id: "52", name: "Food & Drink" }] + }, + "4": {} + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + const userData = bidsConfig.ortb2Fragments.global?.user?.data; + expect(contentData, "should have content data").to.exist; + expect(userData, "should not inject empty audience data").to.be.undefined; + }); + + it("should not inject empty content data when only audience segments exist", function () { + const response = { + "6": {}, + "4": { + "3": [{ id: "49", name: "Female" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data; + const userData = bidsConfig.ortb2Fragments.global?.user?.data?.[0]; + expect(contentData, "should not inject empty content data").to.be.undefined; + expect(userData, "should have audience data").to.exist; + }); + + it("should handle different IAB Content Taxonomy versions", function () { + const response = { + "7": { + "1": [{ id: "80DV8O", name: "Automotive" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "3.0"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData, "should have content data").to.exist; + expect(contentData.ext.segtax, "should use segtax 7 for IAB 3.0").to.equal(7); + }); + + it("should not inject data when segtax has no segments", function () { + const response1 = { "6": {} }; + const response2 = { "4": {} }; + const response3 = { "6": { "1": [] }, "4": { "3": [] } }; + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + + neuwo.injectIabCategories(response1, bidsConfig1, "2.2"); + neuwo.injectIabCategories(response2, bidsConfig2, "2.2"); + neuwo.injectIabCategories(response3, bidsConfig3, "2.2"); + + expect(bidsConfig1.ortb2Fragments.global?.site?.content?.data, "should not inject empty content data").to.be.undefined; + expect(bidsConfig2.ortb2Fragments.global?.user?.data, "should not inject empty audience data").to.be.undefined; + expect(bidsConfig3.ortb2Fragments.global?.site?.content?.data, "should not inject content data with empty segments").to.be.undefined; + expect(bidsConfig3.ortb2Fragments.global?.user?.data, "should not inject audience data with empty segments").to.be.undefined; + }); + + it("should use default taxonomy version when invalid version provided", function () { + const response = { + "6": { + "1": [{ id: "52", name: "Food & Drink" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "invalid-version"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData, "should have content data").to.exist; + expect(contentData.ext.segtax, "should default to segtax 6 (IAB 2.2)").to.equal(6); + }); + + // OpenRTB 2.5 Category Fields Tests + describe("OpenRTB 2.5 category fields", function () { + describe("with enableOrtb25Fields enabled (default)", function () { + it("should inject category fields when IAB 1.0 data exists", function () { + const response = { + "1": { + "1": [{ id: "IAB12" }], + "2": [{ id: "IAB12-3" }, { id: "IAB12-5" }] + }, + "6": { + "1": [{ id: "52" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + const siteSectioncat = bidsConfig.ortb2Fragments.global?.site?.sectioncat; + const sitePagecat = bidsConfig.ortb2Fragments.global?.site?.pagecat; + const contentCat = bidsConfig.ortb2Fragments.global?.site?.content?.cat; + + expect(siteCat, "should have site.cat").to.deep.equal(["IAB12", "IAB12-3", "IAB12-5"]); + expect(siteSectioncat, "should have site.sectioncat").to.deep.equal(["IAB12", "IAB12-3", "IAB12-5"]); + expect(sitePagecat, "should have site.pagecat").to.deep.equal(["IAB12", "IAB12-3", "IAB12-5"]); + expect(contentCat, "should have site.content.cat").to.deep.equal(["IAB12", "IAB12-3", "IAB12-5"]); }); - expect(result, "should remove only specified params").to.equal(expected); - }); - it("should handle single parameter stripping", function () { - const url = "https://example.com/page?remove=this&keep=that"; - const expected = "https://example.com/page?keep=that"; - const result = neuwo.cleanUrl(url, { - stripQueryParams: ["remove"] + it("should inject category fields with single IAB 1.0 segment", function () { + const response = { + "1": { + "1": [{ id: "IAB12" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + expect(siteCat, "should have single category").to.deep.equal(["IAB12"]); }); - expect(result, "should remove single specified param").to.equal(expected); - }); - it("should return URL without query string if all params are stripped", function () { - const url = "https://example.com/page?foo=bar&baz=qux"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { - stripQueryParams: ["foo", "baz"] + it("should not inject category fields when IAB 1.0 data is missing", function () { + const response = { + "6": { + "1": [{ id: "52" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + const siteSectioncat = bidsConfig.ortb2Fragments.global?.site?.sectioncat; + + expect(siteCat, "should not have site.cat").to.be.undefined; + expect(siteSectioncat, "should not have site.sectioncat").to.be.undefined; + }); + + it("should not inject category fields when IAB 1.0 data is empty", function () { + const response = { + "1": {}, + "6": { + "1": [{ id: "52" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + expect(siteCat, "should not have site.cat with empty IAB 1.0").to.be.undefined; + }); + + it("should not inject category fields when IAB 1.0 tiers are empty arrays", function () { + const response = { + "1": { + "1": [], + "2": [] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + expect(siteCat, "should not have site.cat with empty tier arrays").to.be.undefined; + }); + + it("should handle IAB 1.0 data with malformed items", function () { + const response = { + "1": { + "1": [ + { id: "IAB12" }, + { name: "No ID" }, + null, + { id: "IAB12-3" } + ] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + expect(siteCat, "should skip malformed items").to.deep.equal(["IAB12", "IAB12-3"]); + }); + + it("should inject both content data and category fields", function () { + const response = { + "1": { + "1": [{ id: "IAB12" }] + }, + "6": { + "1": [{ id: "52", name: "Food & Drink" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + + expect(contentData, "should have content data").to.exist; + expect(contentData.ext.segtax, "should have segtax 6").to.equal(6); + expect(siteCat, "should have category fields").to.deep.equal(["IAB12"]); + }); + + it("should merge category fields with existing data", function () { + const response = { + "1": { + "1": [{ id: "IAB12" }] + } + }; + const bidsConfig = bidsConfiglike(); + // Pre-populate with existing category data + bidsConfig.ortb2Fragments.global = { + site: { + cat: ["EXISTING1"] + } + }; + + neuwo.injectIabCategories(response, bidsConfig, "2.2"); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + // mergeDeep should deduplicate and merge arrays + expect(siteCat, "should merge with existing data").to.include("IAB12"); + expect(siteCat, "should preserve existing data").to.include("EXISTING1"); }); - expect(result, "should remove query string when all params stripped").to.equal(expected); }); - it("should handle case where specified params do not exist", function () { - const url = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { - stripQueryParams: ["nonexistent", "alsonothere"] + describe("with enableOrtb25Fields disabled", function () { + it("should not inject category fields when disabled", function () { + const response = { + "1": { + "1": [{ id: "IAB12" }], + "2": [{ id: "IAB12-3" }] + }, + "6": { + "1": [{ id: "52" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2", false); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + const siteSectioncat = bidsConfig.ortb2Fragments.global?.site?.sectioncat; + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + + expect(siteCat, "should not have site.cat").to.be.undefined; + expect(siteSectioncat, "should not have site.sectioncat").to.be.undefined; + expect(contentData, "should still have content data (segtax 6)").to.exist; }); - expect(result, "should handle non-existent params gracefully").to.equal(url); + + it("should inject content data but not category fields when disabled", function () { + const response = { + "1": { + "1": [{ id: "IAB12" }] + }, + "6": { + "1": [{ id: "52", name: "Food & Drink" }] + } + }; + const bidsConfig = bidsConfiglike(); + + neuwo.injectIabCategories(response, bidsConfig, "2.2", false); + + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + + expect(contentData, "should have content data").to.exist; + expect(contentData.ext.segtax).to.equal(6); + expect(siteCat, "should not have category fields").to.be.undefined; + }); + }); + }); + }); + + describe("getBidRequestData", function () { + it("should call callback and not make API request when no URL is available", function () { + const getRefererInfoStub = sinon.stub(refererDetection, "getRefererInfo").returns({ page: "" }); + const bidsConfig = bidsConfiglike(); + const conf = config(); + let callbackCalled = false; + + neuwo.getBidRequestData(bidsConfig, () => { callbackCalled = true; }, conf, "consent data"); + + expect(callbackCalled, "callback should be called for empty URL").to.be.true; + expect(server.requests.length, "should not make API request for empty URL").to.equal(0); + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data; + expect(contentData, "should not inject any data").to.be.undefined; + + getRefererInfoStub.restore(); + }); + + it("should call callback when response processing throws an error", function (done) { + const bidsConfig = { ortb2Fragments: null }; + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/processing-error"; + + neuwo.getBidRequestData(bidsConfig, () => { + const contentData = bidsConfig.ortb2Fragments?.global?.site?.content?.data; + expect(contentData, "should not inject data after processing error").to.be.undefined; + done(); + }, conf, "consent data"); + + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(getNeuwoApiResponse()) + ); + }); + + describe("when using IAB Content Taxonomy 2.2 (API default)", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + expect(request.url, "The request URL should include the product identifier").to.include( + "_neuwo_prod=PrebidModule" + ); + expect(request.url, "API should include iabVersions parameter for segtax 6").to.include( + "iabVersions=6" + ); + expect(request.url, "API should include iabVersions parameter for segtax 4").to.include( + "iabVersions=4" + ); + expect(request.method, "API should use GET method").to.equal("GET"); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should correspond to IAB Content Taxonomy 2.2" + ).to.equal(6); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse["6"]["1"][0].id); + expect( + contentData.segment[1].id, + "The second segment ID should match the API response" + ).to.equal(apiResponse["6"]["2"][0].id); + }); + }); + + describe("when using IAB Content Taxonomy 1.0", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = { + 1: { 1: [{ id: "IAB1" }], 2: [{ id: "IAB1-1" }], 3: [{ id: "IAB1-1-1" }] }, + 4: { 3: [{ id: "49" }, { id: "780" }], 4: [{ id: "431" }], 5: [{ id: "98" }] }, + }; + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.iabContentTaxonomyVersion = "1.0"; + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + expect(request.url, "The request URL should include the product identifier").to.include( + "_neuwo_prod=PrebidModule" + ); + expect(request.url, "API should include iabVersions parameter for segtax 1").to.include( + "iabVersions=1" + ); + expect(request.url, "API should include iabVersions parameter for segtax 4").to.include( + "iabVersions=4" + ); + expect(request.method, "API should use GET method").to.equal("GET"); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should correspond to IAB Content Taxonomy 1.0" + ).to.equal(1); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse["1"]["1"][0].id); + expect( + contentData.segment[1].id, + "The second segment ID should match the API response" + ).to.equal(apiResponse["1"]["2"][0].id); + }); + }); + + describe("when using IAB Content Taxonomy 3.0", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = { + 7: { 1: [{ id: "80DV8O" }], 2: [{ id: "90" }], 3: [{ id: "106" }] }, + 4: { 3: [{ id: "49" }, { id: "780" }], 4: [{ id: "431" }], 5: [{ id: "98" }] }, + }; + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.iabContentTaxonomyVersion = "3.0"; + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + expect(request.url, "The request URL should include the product identifier").to.include( + "_neuwo_prod=PrebidModule" + ); + expect(request.url, "API should include iabVersions parameter for segtax 7").to.include( + "iabVersions=7" + ); + expect(request.url, "API should include iabVersions parameter for segtax 4").to.include( + "iabVersions=4" + ); + expect(request.method, "API should use GET method").to.equal("GET"); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should correspond to IAB Content Taxonomy 3.0" + ).to.equal(7); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse["7"]["1"][0].id); + expect( + contentData.segment[1].id, + "The second segment ID should match the API response" + ).to.equal(apiResponse["7"]["2"][0].id); + }); + }); + + describe("when using IAB Audience Taxonomy 1.1", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + expect(request.method, "API should use GET method").to.equal("GET"); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + const userData = bidsConfig.ortb2Fragments.global.user.data[0]; + expect(userData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + userData.ext.segtax, + "The segtax value should correspond to IAB Audience Taxonomy 1.1" + ).to.equal(4); + expect( + userData.segment[0].id, + "The first segment ID should match the API response (tier 3, first item)" + ).to.equal(apiResponse["4"]["3"][0].id); + expect( + userData.segment[1].id, + "The second segment ID should match the API response (tier 3, second item)" + ).to.equal(apiResponse["4"]["3"][1].id); + }); + }); + + it("should not change the bids object structure after an unsuccessful API response", function () { + const bidsConfig = bidsConfiglike(); + const bidsConfigCopy = bidsConfiglike(); + const conf = config(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 404, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify({ detail: "test error" }) + ); + expect( + bidsConfig, + "The bids config object should remain unmodified after a failed API call" + ).to.deep.equal(bidsConfigCopy); + }); + + // OpenRTB 2.5 Feature Tests + describe("OpenRTB 2.5 category fields", function () { + describe("with enableOrtb25Fields enabled (default)", function () { + it("should include iabVersions=1 parameter in API request", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "should include iabVersions=1").to.include("iabVersions=1"); + expect(request.url, "should include iabVersions=6").to.include("iabVersions=6"); + expect(request.url, "should include iabVersions=4").to.include("iabVersions=4"); + }); + + it("should not duplicate iabVersions=1 when iabContentTaxonomyVersion is 1.0", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + conf.params.iabContentTaxonomyVersion = "1.0"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + const matches = request.url.match(/iabVersions=1(?!\d)/g) || []; + expect(matches.length, "iabVersions=1 should appear exactly once").to.equal(1); + expect(request.url, "should still include iabVersions=4").to.include("iabVersions=4"); + }); + + it("should inject category fields when API returns IAB 1.0 data", function () { + const apiResponse = { + "1": { + "1": [{ id: "IAB12" }], + "2": [{ id: "IAB12-3" }] + }, + "6": { + "1": [{ id: "52" }] + }, + "4": { + "3": [{ id: "49" }] + } + }; + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + + expect(siteCat, "should have site.cat").to.deep.equal(["IAB12", "IAB12-3"]); + expect(contentData, "should have content data").to.exist; + }); + + it("should send IAB 1.0 filter configuration in URL parameters", function () { + const apiResponse = { + "1": { + "1": [{ id: "IAB12" }], + "2": [{ id: "IAB12-3" }] + }, + "6": { + "1": [{ id: "52" }] + } + }; + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + conf.params.iabTaxonomyFilters = { + ContentTier1: { limit: 2 } + }; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + + const request = server.requests[0]; + expect(request.url, "should have filter for segtax 1 tier 1").to.include("filter_1_1_limit=2"); + expect(request.url, "should have filter for segtax 6 tier 1").to.include("filter_6_1_limit=2"); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + expect(siteCat, "should inject category fields").to.deep.equal(["IAB12", "IAB12-3"]); + }); + }); + + describe("with enableOrtb25Fields disabled", function () { + it("should not include iabVersions=1 parameter in API request", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + conf.params.enableOrtb25Fields = false; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "should not include iabVersions=1").to.not.include("iabVersions=1"); + expect(request.url, "should still include iabVersions=6").to.include("iabVersions=6"); + }); + + it("should not inject category fields even if API returns IAB 1.0 data", function () { + const apiResponse = { + "1": { + "1": [{ id: "IAB12" }] + }, + "6": { + "1": [{ id: "52" }] + } + }; + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + conf.params.enableOrtb25Fields = false; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + + expect(siteCat, "should not have site.cat").to.be.undefined; + expect(contentData, "should still have content data").to.exist; + }); + + it("should not send IAB 1.0 filters in URL parameters", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + conf.params.enableOrtb25Fields = false; + conf.params.iabTaxonomyFilters = { + ContentTier1: { limit: 3 }, + ContentTier2: { limit: 5 } + }; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "should not have segtax 1 filters").to.not.match(/filter_1_/); + expect(request.url, "should have segtax 6 filters").to.include("filter_6_1_limit=3"); + expect(request.url, "should have segtax 6 tier 2 filters").to.include("filter_6_2_limit=5"); + }); + }); + }); + }); + + describe("cleanUrl", function () { + describe("when no stripping options are provided", function () { + it("should return the URL unchanged", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const result = neuwo.cleanUrl(url, {}); + expect(result, "should return the original URL with all query params intact").to.equal(url); + }); + + it("should return the URL unchanged when options object is empty", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url); + expect(result, "should handle missing options parameter").to.equal(url); + }); + }); + + describe("with query parameters edge cases", function () { + it("should strip all query parameters from the URL for `stripAllQueryParams` (edge cases)", function () { + const stripAll = (url) => neuwo.cleanUrl(url, { stripAllQueryParams: true }); + const expected = "https://example.com/page"; + const expectedWithFragment = "https://example.com/page#anchor"; + + // Basic formats + expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); + expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); + expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); + expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + + // Multiple parameters + expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); + expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + + // Special characters and encoding + expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); + expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); + expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + + // Delimiters and syntax edge cases + expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); + expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + + // Empty and missing cases + expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); + expect(stripAll("https://example.com/page??"), "should remove double question mark").to.equal(expected); + expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + + // Unicode and special values + expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); + expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); + expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + + // Fragment positioning (fragments are preserved by default) + expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); + expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + }); + + it("should strip all query parameters from the URL for `stripQueryParamsForDomains` (edge cases)", function () { + const stripAll = (url) => neuwo.cleanUrl(url, { stripQueryParamsForDomains: ["example.com"] }); + const expected = "https://example.com/page"; + const expectedWithFragment = "https://example.com/page#anchor"; + + // Basic formats + expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); + expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); + expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); + expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + + // Multiple parameters + expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); + expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + + // Special characters and encoding + expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); + expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); + expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + + // Delimiters and syntax edge cases + expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); + expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + + // Empty and missing cases + expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); + expect(stripAll("https://example.com/page??"), "should remove double question mark").to.equal(expected); + expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + + // Unicode and special values + expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); + expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); + expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + + // Fragment positioning (fragments are preserved by default) + expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); + expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + }); + + it("should strip all query parameters from the URL for `stripQueryParams` (edge cases)", function () { + const stripAll = (url) => neuwo.cleanUrl(url, { stripQueryParams: ["key", "key1", "key2", "", "?"] }); + const expected = "https://example.com/page"; + const expectedWithFragment = "https://example.com/page#anchor"; + + // Basic formats + expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); + expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); + expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); + expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + + // Multiple parameters + expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); + expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + + // Special characters and encoding + expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); + expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); + expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + + // Delimiters and syntax edge cases + expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); + expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + + // Empty and missing cases + expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); + expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + + // Unicode and special values + expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); + expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); + expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + + // Fragment positioning (fragments are preserved by default) + expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); + expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + }); + }); + + describe("when stripAllQueryParams is true", function () { + it("should strip all query parameters from the URL", function () { + const url = "https://example.com/page?foo=bar&baz=qux&test=123"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should remove all query parameters").to.equal(expected); + }); + + it("should return the URL unchanged if there are no query parameters", function () { + const url = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should handle URLs without query params").to.equal(url); + }); + + it("should preserve the hash fragment when stripping query params without stripFragments", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page#section"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should preserve hash fragments by default").to.equal(expected); + }); + + it("should strip hash fragment when stripFragments is enabled", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true, stripFragments: true }); + expect(result, "should strip both query params and fragments").to.equal(expected); + }); + + it("should strip query params but preserve path and protocol", function () { + const url = "https://subdomain.example.com:8080/path/to/page?param=value"; + const expected = "https://subdomain.example.com:8080/path/to/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should preserve protocol, domain, port, and path").to.equal(expected); + }); + }); + + describe("when stripQueryParamsForDomains is provided", function () { + it("should strip all query params for exact domain match", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should strip params for exact domain match").to.equal(expected); + }); + + it("should strip all query params for subdomain match", function () { + const url = "https://sub.example.com/page?foo=bar"; + const expected = "https://sub.example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should strip params for subdomains").to.equal(expected); + }); + + it("should not strip query params if domain does not match", function () { + const url = "https://other.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should preserve params for non-matching domains").to.equal(url); + }); + + it("should not strip query params if subdomain is provided for domain", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["sub.example.com"] + }); + expect(result, "should preserve params for domain when subdomain is provided").to.equal(url); + }); + + it("should handle multiple domains in the list", function () { + const url1 = "https://example.com/page?foo=bar"; + const url2 = "https://test.com/page?foo=bar"; + const url3 = "https://other.com/page?foo=bar"; + const domains = ["example.com", "test.com"]; + + const result1 = neuwo.cleanUrl(url1, { stripQueryParamsForDomains: domains }); + const result2 = neuwo.cleanUrl(url2, { stripQueryParamsForDomains: domains }); + const result3 = neuwo.cleanUrl(url3, { stripQueryParamsForDomains: domains }); + + expect(result1, "should strip params for first domain").to.equal("https://example.com/page"); + expect(result2, "should strip params for second domain").to.equal("https://test.com/page"); + expect(result3, "should preserve params for non-listed domain").to.equal(url3); + }); + + it("should handle deep subdomains correctly", function () { + const url = "https://deep.sub.example.com/page?foo=bar"; + const expected = "https://deep.sub.example.com/page"; + const result1 = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + const result2 = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["sub.example.com"] + }); + expect(result1, "should strip params for deep subdomains with domain matching").to.equal(expected); + expect(result2, "should strip params for deep subdomains with subdomain matching").to.equal(expected); + }); + + it("should not match partial domain names", function () { + const url = "https://notexample.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should not match partial domain strings").to.equal(url); + }); + + it("should handle empty domain list", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripQueryParamsForDomains: [] }); + expect(result, "should not strip params with empty domain list").to.equal(url); + }); + }); + + describe("when stripQueryParams is provided", function () { + it("should strip only specified query parameters", function () { + const url = "https://example.com/page?foo=bar&baz=qux&keep=this"; + const expected = "https://example.com/page?keep=this"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["foo", "baz"] + }); + expect(result, "should remove only specified params").to.equal(expected); + }); + + it("should handle single parameter stripping", function () { + const url = "https://example.com/page?remove=this&keep=that"; + const expected = "https://example.com/page?keep=that"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["remove"] + }); + expect(result, "should remove single specified param").to.equal(expected); + }); + + it("should return URL without query string if all params are stripped", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["foo", "baz"] + }); + expect(result, "should remove query string when all params stripped").to.equal(expected); + }); + + it("should handle case where specified params do not exist", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["nonexistent", "alsonothere"] + }); + expect(result, "should handle non-existent params gracefully").to.equal(url); + }); + + it("should handle empty param list", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripQueryParams: [] }); + expect(result, "should not strip params with empty list").to.equal(url); + }); + + it("should preserve param order for remaining params", function () { + const url = "https://example.com/page?a=1&b=2&c=3&d=4"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["b", "d"] + }); + expect(result, "should preserve order of remaining params").to.include("a=1"); + expect(result, "should preserve order of remaining params").to.include("c=3"); + expect(result, "should not include stripped param b").to.not.include("b=2"); + expect(result, "should not include stripped param d").to.not.include("d=4"); + }); + }); + + describe("error handling", function () { + it("should return null or undefined input unchanged", function () { + expect(neuwo.cleanUrl(null, {}), "should handle null input").to.equal(null); + expect(neuwo.cleanUrl(undefined, {}), "should handle undefined input").to.equal(undefined); + expect(neuwo.cleanUrl("", {}), "should handle empty string").to.equal(""); + }); + + it("should return invalid URL unchanged and log error", function () { + const invalidUrl = "not-a-valid-url"; + const result = neuwo.cleanUrl(invalidUrl, { stripAllQueryParams: true }); + expect(result, "should return invalid URL unchanged").to.equal(invalidUrl); + }); + + it("should handle malformed URLs gracefully", function () { + const malformedUrl = "http://"; + const result = neuwo.cleanUrl(malformedUrl, { stripAllQueryParams: true }); + expect(result, "should return malformed URL unchanged").to.equal(malformedUrl); + }); + }); + + describe("when stripFragments is enabled", function () { + it("should strip URL fragments from URLs without query params", function () { + const url = "https://example.com/page#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove hash fragment").to.equal(expected); + }); + + it("should strip URL fragments from URLs with query params", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove hash fragment and preserve query params").to.equal(expected); + }); + + it("should strip fragments when combined with stripAllQueryParams", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true, stripFragments: true }); + expect(result, "should remove both query params and fragment").to.equal(expected); + }); + + it("should strip fragments when combined with stripQueryParamsForDomains", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"], + stripFragments: true + }); + expect(result, "should remove both query params and fragment for matching domain").to.equal(expected); + }); + + it("should strip fragments when combined with stripQueryParams", function () { + const url = "https://example.com/page?foo=bar&keep=this#section"; + const expected = "https://example.com/page?keep=this"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["foo"], + stripFragments: true + }); + expect(result, "should remove specified query params and fragment").to.equal(expected); + }); + + it("should handle URLs without fragments gracefully", function () { + const url = "https://example.com/page?foo=bar"; + const expected = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should handle URLs without fragments").to.equal(expected); + }); + + it("should handle empty fragments", function () { + const url = "https://example.com/page#"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove empty fragment").to.equal(expected); + }); + + it("should handle complex fragments with special characters", function () { + const url = "https://example.com/page?foo=bar#section-1/subsection?query"; + const expected = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove complex fragments").to.equal(expected); + }); + }); + + describe("option priority", function () { + it("should apply stripAllQueryParams first when multiple options are set", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripAllQueryParams: true, + stripQueryParams: ["foo"] + }); + expect(result, "stripAllQueryParams should take precedence").to.equal(expected); + }); + + it("should apply stripQueryParamsForDomains before stripQueryParams", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"], + stripQueryParams: ["foo"] + }); + expect(result, "domain-specific stripping should take precedence").to.equal(expected); + }); + + it("should not strip for non-matching domain even with stripQueryParams set", function () { + const url = "https://other.com/page?foo=bar&baz=qux"; + const expected = "https://other.com/page?baz=qux"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"], + stripQueryParams: ["foo"] + }); + expect(result, "should fall through to stripQueryParams for non-matching domain").to.equal(expected); + }); + }); + }); + + // Integration Tests + describe("injectIabCategories edge cases and merging", function () { + it("should not inject data if response contains no segments", function () { + const apiResponse = { "6": {}, "4": {} }; // Empty response (no segments) + const bidsConfig = bidsConfiglike(); + const bidsConfigCopy = bidsConfiglike(); + const conf = config(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // After a successful response with no segments, the global ortb2 fragments should remain empty + // as the data injection logic only injects when segments exist + expect( + bidsConfig.ortb2Fragments.global, + "The global ORTB fragments should remain empty" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + }); + + it("should append content and user data to existing ORTB fragments", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + // Simulate existing first-party data from another source/module + const existingContentData = { name: "other_content_provider", segment: [{ id: "1" }] }; + const existingUserData = { name: "other_user_provider", segment: [{ id: "2" }] }; + + bidsConfig.ortb2Fragments.global = { + site: { + content: { + data: [existingContentData], + }, + }, + user: { + data: [existingUserData], + }, + }; + const conf = config(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const siteData = bidsConfig.ortb2Fragments.global.site.content.data; + const userData = bidsConfig.ortb2Fragments.global.user.data; + + // Check that the existing data is still there (index 0) + expect(siteData[0], "Existing site.content.data should be preserved").to.deep.equal( + existingContentData + ); + expect(userData[0], "Existing user.data should be preserved").to.deep.equal(existingUserData); + + // Check that the new Neuwo data is appended (index 1) + expect(siteData.length, "site.content.data array should have 2 entries").to.equal(2); + expect(userData.length, "user.data array should have 2 entries").to.equal(2); + expect(siteData[1].name, "The appended content data should be from Neuwo").to.equal( + neuwo.DATA_PROVIDER + ); + expect(userData[1].name, "The appended user data should be from Neuwo").to.equal( + neuwo.DATA_PROVIDER + ); + }); + + it("should correctly construct API URL when neuwoApiUrl already contains query parameters", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + // Set API URL that already has query parameters + conf.params.neuwoApiUrl = "https://edge.neuwo.ai/api/aitopics/edge/v1/iab?environment=production"; + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + // Should use & as joiner instead of ? + expect(request.url, "URL should contain environment param from base URL").to.include("environment=production"); + expect(request.url, "URL should contain token param joined with &").to.include("&token="); + expect(request.url, "URL should contain url param").to.include("&url="); + expect(request.url, "URL should contain product identifier").to.include("&_neuwo_prod=PrebidModule"); + expect(request.url, "URL should include iabVersions parameter").to.include("iabVersions="); + // Should not have ?? in the URL + expect(request.url, "URL should not contain double question marks").to.not.include("??"); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "Should successfully process response").to.equal(neuwo.DATA_PROVIDER); + }); + + it("should treat a legacy URL with /v1/iab in query params as a legacy endpoint", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + // Proxy URL where /v1/iab appears in query params, not the path + conf.params.neuwoApiUrl = "https://proxy.example.com/api?redirect=/v1/iab"; + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + // Legacy endpoints should NOT include iabVersions params + expect(request.url, "should not include iabVersions for legacy endpoint").to.not.include("iabVersions="); + // Should still include token and url params + expect(request.url, "should include token param").to.include("token="); + expect(request.url, "should include url param").to.include("url="); + }); + + it("should detect /v1/iab endpoint from a malformed URL using fallback parsing", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + // Malformed URL that causes new URL() to throw, but has /v1/iab in path + conf.params.neuwoApiUrl = "/v1/iab"; + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + // Fallback parser should detect /v1/iab in path portion and treat as IAB endpoint + expect(request.url, "should include iabVersions for IAB endpoint").to.include("iabVersions="); + }); + }); + + describe("getBidRequestData with caching", function () { + describe("when enableCache is true (default)", function () { + it("should cache the API response and reuse it on subsequent calls", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=1"; + + // First call should make an API request + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call should use cached response (no new API request) + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should not make a new API request").to.equal(1); + + // Both configs should have identical data (second served from cache) + const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; + expect(contentData1, "First config should have Neuwo data").to.exist; + expect(contentData2, "Second config should have Neuwo data from cache").to.exist; + expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData1.segment, "Cached data should have same segments as original").to.deep.equal(contentData2.segment); + }); + + it("should cache when enableCache is explicitly set to true", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=2"; + conf.params.enableCache = true; + + // First call + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should use cached response").to.equal(1); + }); + + it("should handle concurrent requests by sharing a pending request promise", function (done) { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=concurrent"; + conf.params.enableCache = true; + + let callbackCount = 0; + const callback = () => { + callbackCount++; + if (callbackCount === 3) { + // All callbacks have been called, now verify the data + try { + const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; + const contentData3 = bidsConfig3.ortb2Fragments.global.site.content.data[0]; + + expect(contentData1, "First config should have Neuwo data").to.exist; + expect(contentData2, "Second config should have Neuwo data from pending request").to.exist; + expect(contentData3, "Third config should have Neuwo data from pending request").to.exist; + expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData3.name, "Third config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + done(); + } catch (e) { + done(e); + } + } + }; + + // Make three concurrent calls before responding to the first request + neuwo.getBidRequestData(bidsConfig1, callback, conf, "consent data"); + neuwo.getBidRequestData(bidsConfig2, callback, conf, "consent data"); + neuwo.getBidRequestData(bidsConfig3, callback, conf, "consent data"); + + // Only one API request should be made + expect(server.requests.length, "Only one API request should be made for concurrent calls").to.equal(1); + + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + }); + + it("should transition through all three cache states: pending request, then cached response", function (done) { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=three-stage"; + conf.params.enableCache = true; + + let callback1and2Count = 0; + + const callback1and2 = () => { + callback1and2Count++; + if (callback1and2Count === 2) { + // Both first and second callbacks have been called + // Stage 3: Third request should use cached response (not pending request) + neuwo.getBidRequestData(bidsConfig3, () => { + try { + expect(server.requests.length, "Third call should use cache and not make a new API request").to.equal(1); + + // All three configs should have the same data + const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; + const contentData3 = bidsConfig3.ortb2Fragments.global.site.content.data[0]; + + expect(contentData1, "First config should have Neuwo data").to.exist; + expect(contentData2, "Second config should have Neuwo data from pending request").to.exist; + expect(contentData3, "Third config should have Neuwo data from cache").to.exist; + expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData3.name, "Third config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + done(); + } catch (e) { + done(e); + } + }, conf, "consent data"); + } + }; + + // Stage 1: First request initiates API call (creates pending request) + neuwo.getBidRequestData(bidsConfig1, callback1and2, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + // Stage 2: Second request should attach to pending request before response + neuwo.getBidRequestData(bidsConfig2, callback1and2, conf, "consent data"); + expect(server.requests.length, "Second call should not make a new API request").to.equal(1); + + // Respond to the API request, which populates the cache + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + }); + + it("should not share cache between requests with different parameters", function (done) { + const apiResponse1 = getNeuwoApiResponse(); + const apiResponse2 = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf1 = config(); + conf1.params.websiteToAnalyseUrl = "https://publisher.works/page-a"; + conf1.params.enableCache = true; + const conf2 = config(); + conf2.params.websiteToAnalyseUrl = "https://publisher.works/page-b"; + conf2.params.enableCache = true; + + let callbackCount = 0; + const callback = () => { + callbackCount++; + if (callbackCount === 2) { + try { + // Both should have made separate API requests + expect(server.requests.length, "Should make two separate API requests for different URLs").to.equal(2); + expect(server.requests[0].url).to.contain("page-a"); + expect(server.requests[1].url).to.contain("page-b"); + done(); + } catch (e) { + done(e); + } + } + }; + + // Two concurrent calls with different URLs + neuwo.getBidRequestData(bidsConfig1, callback, conf1, "consent data"); + neuwo.getBidRequestData(bidsConfig2, callback, conf2, "consent data"); + + expect(server.requests.length, "Should make two separate API requests").to.equal(2); + + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse1) + ); + server.requests[1].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse2) + ); + }); + + it("should use cache for same URL but make new request after config change", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-a"; + conf.params.enableCache = true; + + // First call + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length).to.equal(1); + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call with same URL - should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Same URL should use cache").to.equal(1); + + // Third call with different URL (simulating config change) - should make new request + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-b"; + neuwo.getBidRequestData(bidsConfig3, () => {}, conf, "consent data"); + expect(server.requests.length, "Different URL should make new request").to.equal(2); + expect(server.requests[1].url).to.contain("page-b"); + }); + + it("should not share cache when iabContentTaxonomyVersion changes", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf.params.enableCache = true; + conf.params.iabContentTaxonomyVersion = "2.2"; + + // First call with taxonomy 2.2 + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.contain("iabVersions=6"); // segtax 6 = taxonomy 2.2 + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call with same taxonomy - should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Same taxonomy version should use cache").to.equal(1); + + // Third call with different taxonomy version - should make new request + conf.params.iabContentTaxonomyVersion = "3.0"; + neuwo.getBidRequestData(bidsConfig3, () => {}, conf, "consent data"); + expect(server.requests.length, "Different taxonomy version should make new request").to.equal(2); + expect(server.requests[1].url).to.contain("iabVersions=7"); // segtax 7 = taxonomy 3.0 + }); + + it("should evict the oldest cache entry when MAX_CACHE_ENTRIES is exceeded", async function () { + const apiResponse = getNeuwoApiResponse(); + const conf = config(); + conf.params.enableCache = true; + + // Fill cache with 10 entries (MAX_CACHE_ENTRIES) + for (let i = 0; i < 10; i++) { + const bidsConfig = bidsConfiglike(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-" + i; + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + server.requests[i].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + } + // Flush microtasks so pendingRequests are cleaned up via .finally() handlers + await Promise.resolve(); + + expect(server.requests.length, "Should have made 10 API requests").to.equal(10); + + // Verify page-0 is cached + const bidsConfigCached = bidsConfiglike(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-0"; + neuwo.getBidRequestData(bidsConfigCached, () => {}, conf, "consent data"); + expect(server.requests.length, "page-0 should be served from cache").to.equal(10); + + // Add 11th entry to trigger eviction of the oldest (page-0) + const bidsConfig11 = bidsConfiglike(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-10"; + neuwo.getBidRequestData(bidsConfig11, () => {}, conf, "consent data"); + expect(server.requests.length, "page-10 should trigger new request").to.equal(11); + server.requests[10].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + await Promise.resolve(); + + // page-0 should have been evicted and require a new request + const bidsConfigEvicted = bidsConfiglike(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-0"; + neuwo.getBidRequestData(bidsConfigEvicted, () => {}, conf, "consent data"); + expect(server.requests.length, "page-0 should be evicted and trigger new request").to.equal(12); + + // page-1 should still be cached + const bidsConfigStillCached = bidsConfiglike(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/page-1"; + neuwo.getBidRequestData(bidsConfigStillCached, () => {}, conf, "consent data"); + expect(server.requests.length, "page-1 should still be in cache").to.equal(12); + }); + }); + + describe("when enableCache is false", function () { + it("should not cache the API response and make a new request each time", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=3"; + conf.params.enableCache = false; + + // First call should make an API request + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call should make a new API request (not use cache) + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should make a new API request").to.equal(2); + + const request2 = server.requests[1]; + request2.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Both configs should have the same data structure + const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; + expect(contentData1, "First config should have Neuwo data").to.exist; + expect(contentData2, "Second config should have Neuwo data from new request").to.exist; + expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + }); + + it("should bypass existing cache when enableCache is false", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=4"; + + // First call with caching enabled (default) + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call with caching enabled should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should use cache").to.equal(1); + + // Third call with caching disabled should bypass cache + conf.params.enableCache = false; + neuwo.getBidRequestData(bidsConfig3, () => {}, conf, "consent data"); + expect(server.requests.length, "Third call should bypass cache and make new request").to.equal(2); + + const request2 = server.requests[1]; + request2.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); }); - it("should handle empty param list", function () { - const url = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { stripQueryParams: [] }); - expect(result, "should not strip params with empty list").to.equal(url); - }); + it("should clear pending request after error response and retry on next call", function (done) { + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=error-test"; + conf.params.enableCache = true; - it("should preserve param order for remaining params", function () { - const url = "https://example.com/page?a=1&b=2&c=3&d=4"; - const result = neuwo.cleanUrl(url, { - stripQueryParams: ["b", "d"] - }); - expect(result, "should preserve order of remaining params").to.include("a=1"); - expect(result, "should preserve order of remaining params").to.include("c=3"); - expect(result, "should not include stripped param b").to.not.include("b=2"); - expect(result, "should not include stripped param d").to.not.include("d=4"); - }); - }); + // First call - will get 404 error + neuwo.getBidRequestData(bidsConfig1, () => { + // After error, data should not be injected + const contentData = bidsConfig1.ortb2Fragments.global?.site?.content?.data; + expect(contentData, "No data should be injected after error").to.be.undefined; + + // Second call - should retry API (pending should be cleared) + neuwo.getBidRequestData(bidsConfig2, () => { + try { + expect(server.requests.length, "Second call should retry after error").to.equal(2); + const contentData2 = bidsConfig2.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData2, "Second call should have Neuwo data after retry").to.exist; + expect(contentData2.name, "Second call should have correct provider").to.equal(neuwo.DATA_PROVIDER); + done(); + } catch (e) { + done(e); + } + }, conf, "consent data"); + + // Respond with success to second request + const request2 = server.requests[1]; + request2.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(getNeuwoApiResponse()) + ); + }, conf, "consent data"); - describe("error handling", function () { - it("should return null or undefined input unchanged", function () { - expect(neuwo.cleanUrl(null, {}), "should handle null input").to.equal(null); - expect(neuwo.cleanUrl(undefined, {}), "should handle undefined input").to.equal(undefined); - expect(neuwo.cleanUrl("", {}), "should handle empty string").to.equal(""); - }); + expect(server.requests.length, "First call should make an API request").to.equal(1); - it("should return invalid URL unchanged and log error", function () { - const invalidUrl = "not-a-valid-url"; - const result = neuwo.cleanUrl(invalidUrl, { stripAllQueryParams: true }); - expect(result, "should return invalid URL unchanged").to.equal(invalidUrl); + // Respond with error to first request + const request1 = server.requests[0]; + request1.respond( + 404, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify({ error: "Not found" }) + ); }); - it("should handle malformed URLs gracefully", function () { - const malformedUrl = "http://"; - const result = neuwo.cleanUrl(malformedUrl, { stripAllQueryParams: true }); - expect(result, "should return malformed URL unchanged").to.equal(malformedUrl); + it("should handle concurrent requests when API returns error", function (done) { + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=concurrent-error"; + conf.params.enableCache = true; + + let callbackCount = 0; + const callback = () => { + callbackCount++; + if (callbackCount === 3) { + try { + // None of the configs should have data after error + const contentData1 = bidsConfig1.ortb2Fragments.global?.site?.content?.data; + const contentData2 = bidsConfig2.ortb2Fragments.global?.site?.content?.data; + const contentData3 = bidsConfig3.ortb2Fragments.global?.site?.content?.data; + + expect(contentData1, "First config should not have data after error").to.be.undefined; + expect(contentData2, "Second config should not have data after error").to.be.undefined; + expect(contentData3, "Third config should not have data after error").to.be.undefined; + done(); + } catch (e) { + done(e); + } + } + }; + + // Make three concurrent calls + neuwo.getBidRequestData(bidsConfig1, callback, conf, "consent data"); + neuwo.getBidRequestData(bidsConfig2, callback, conf, "consent data"); + neuwo.getBidRequestData(bidsConfig3, callback, conf, "consent data"); + + expect(server.requests.length, "Only one API request should be made").to.equal(1); + + // Respond with error + const request = server.requests[0]; + request.respond( + 500, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify({ error: "Internal server error" }) + ); }); - }); - describe("when stripFragments is enabled", function () { - it("should strip URL fragments from URLs without query params", function () { - const url = "https://example.com/page#section"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { stripFragments: true }); - expect(result, "should remove hash fragment").to.equal(expected); + it("should handle JSON parsing error in success callback", function (done) { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=parse-error"; + conf.params.enableCache = true; + + neuwo.getBidRequestData(bidsConfig, () => { + // Callback should still be called + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data; + expect(contentData, "No data should be injected after parsing error").to.be.undefined; + done(); + }, conf, "consent data"); + + expect(server.requests.length, "Should make an API request").to.equal(1); + + // Respond with invalid JSON + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + "{ invalid json content }" + ); }); - it("should strip URL fragments from URLs with query params", function () { - const url = "https://example.com/page?foo=bar#section"; - const expected = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { stripFragments: true }); - expect(result, "should remove hash fragment and preserve query params").to.equal(expected); + it("should not cache response after JSON parsing error and allow retry", function (done) { + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=parse-error-retry"; + conf.params.enableCache = true; + + neuwo.getBidRequestData(bidsConfig1, () => { + // First call with parsing error + const contentData1 = bidsConfig1.ortb2Fragments.global?.site?.content?.data; + expect(contentData1, "No data after parsing error").to.be.undefined; + + // Second call should retry (not use cached error) + neuwo.getBidRequestData(bidsConfig2, () => { + try { + expect(server.requests.length, "Should retry after parsing error").to.equal(2); + const contentData2 = bidsConfig2.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData2, "Second call should have valid data").to.exist; + expect(contentData2.name, "Should have correct provider").to.equal(neuwo.DATA_PROVIDER); + done(); + } catch (e) { + done(e); + } + }, conf, "consent data"); + + // Second request gets valid response + const request2 = server.requests[1]; + request2.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(getNeuwoApiResponse()) + ); + }, conf, "consent data"); + + // First request gets invalid JSON + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + "{ this is not valid JSON }" + ); }); - it("should strip fragments when combined with stripAllQueryParams", function () { - const url = "https://example.com/page?foo=bar#section"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { stripAllQueryParams: true, stripFragments: true }); - expect(result, "should remove both query params and fragment").to.equal(expected); + it("should handle response with empty segments", function (done) { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=no-categories"; + + neuwo.getBidRequestData(bidsConfig, () => { + // Callback should still be called even with empty response + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data; + expect(contentData, "No data should be injected without segments").to.be.undefined; + done(); + }, conf, "consent data"); + + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify({ "6": {}, "4": {} }) // Empty segments + ); }); + }); - it("should strip fragments when combined with stripQueryParamsForDomains", function () { - const url = "https://example.com/page?foo=bar#section"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"], - stripFragments: true + describe("with URL query param stripping", function () { + describe("when stripAllQueryParams is enabled", function () { + it("should strip all query parameters from the analyzed URL", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test&utm_campaign=example&id=5"; + conf.params.stripAllQueryParams = true; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should not contain encoded query params").to.include( + encodeURIComponent("https://publisher.works/article.php") + ); + expect(request.url, "The request URL should not contain utm_source").to.not.include( + encodeURIComponent("utm_source") + ); }); - expect(result, "should remove both query params and fragment for matching domain").to.equal(expected); }); - it("should strip fragments when combined with stripQueryParams", function () { - const url = "https://example.com/page?foo=bar&keep=this#section"; - const expected = "https://example.com/page?keep=this"; - const result = neuwo.cleanUrl(url, { - stripQueryParams: ["foo"], - stripFragments: true + describe("when stripQueryParamsForDomains is enabled", function () { + it("should strip query params only for matching domains", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?foo=bar&id=5"; + conf.params.stripQueryParamsForDomains = ["publisher.works"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the URL without query params").to.include( + encodeURIComponent("https://publisher.works/article.php") + ); + expect(request.url, "The request URL should not contain the id param").to.not.include( + encodeURIComponent("id=5") + ); }); - expect(result, "should remove specified query params and fragment").to.equal(expected); - }); - it("should handle URLs without fragments gracefully", function () { - const url = "https://example.com/page?foo=bar"; - const expected = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { stripFragments: true }); - expect(result, "should handle URLs without fragments").to.equal(expected); - }); + it("should not strip query params for non-matching domains", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://other-domain.com/page?foo=bar&id=5"; + conf.params.stripQueryParamsForDomains = ["publisher.works"]; - it("should handle empty fragments", function () { - const url = "https://example.com/page#"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { stripFragments: true }); - expect(result, "should remove empty fragment").to.equal(expected); - }); + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; - it("should handle complex fragments with special characters", function () { - const url = "https://example.com/page?foo=bar#section-1/subsection?query"; - const expected = "https://example.com/page?foo=bar"; - const result = neuwo.cleanUrl(url, { stripFragments: true }); - expect(result, "should remove complex fragments").to.equal(expected); - }); - }); + expect(request.url, "The request URL should contain the full URL with query params").to.include( + encodeURIComponent("https://other-domain.com/page?foo=bar&id=5") + ); + }); - describe("option priority", function () { - it("should apply stripAllQueryParams first when multiple options are set", function () { - const url = "https://example.com/page?foo=bar&baz=qux"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { - stripAllQueryParams: true, - stripQueryParams: ["foo"] + it("should handle subdomain matching correctly", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://sub.publisher.works/page?tracking=123"; + conf.params.stripQueryParamsForDomains = ["publisher.works"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should strip params for subdomain").to.include( + encodeURIComponent("https://sub.publisher.works/page") + ); + expect(request.url, "The request URL should not contain tracking param").to.not.include( + encodeURIComponent("tracking=123") + ); }); - expect(result, "stripAllQueryParams should take precedence").to.equal(expected); }); - it("should apply stripQueryParamsForDomains before stripQueryParams", function () { - const url = "https://example.com/page?foo=bar&baz=qux"; - const expected = "https://example.com/page"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"], - stripQueryParams: ["foo"] + describe("when stripQueryParams is enabled", function () { + it("should strip only specified query parameters", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test&utm_campaign=example&id=5"; + conf.params.stripQueryParams = ["utm_source", "utm_campaign"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the id param").to.include( + encodeURIComponent("id=5") + ); + expect(request.url, "The request URL should not contain utm_source").to.not.include( + encodeURIComponent("utm_source") + ); + expect(request.url, "The request URL should not contain utm_campaign").to.not.include( + encodeURIComponent("utm_campaign") + ); }); - expect(result, "domain-specific stripping should take precedence").to.equal(expected); - }); - it("should not strip for non-matching domain even with stripQueryParams set", function () { - const url = "https://other.com/page?foo=bar&baz=qux"; - const expected = "https://other.com/page?baz=qux"; - const result = neuwo.cleanUrl(url, { - stripQueryParamsForDomains: ["example.com"], - stripQueryParams: ["foo"] + it("should handle stripping params that result in no query string", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test"; + conf.params.stripQueryParams = ["utm_source"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should not contain a query string").to.include( + encodeURIComponent("https://publisher.works/article.php") + ); + expect(request.url, "The request URL should not contain utm_source").to.not.include( + encodeURIComponent("utm_source") + ); }); - expect(result, "should fall through to stripQueryParams for non-matching domain").to.equal(expected); - }); - }); - }); - // Integration Tests - describe("injectIabCategories edge cases and merging", function () { - it("should not inject data if 'marketing_categories' is missing from the successful API response", function () { - const apiResponse = { brand_safety: { BS_score: "1.0" } }; // Missing marketing_categories - const bidsConfig = bidsConfiglike(); - const bidsConfigCopy = bidsConfiglike(); - const conf = config(); + it("should leave URL unchanged if specified params do not exist", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + const originalUrl = "https://publisher.works/article.php?id=5"; + conf.params.websiteToAnalyseUrl = originalUrl; + conf.params.stripQueryParams = ["utm_source", "nonexistent"]; - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; - request.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; - // After a successful response with missing data, the global ortb2 fragments should remain empty - // as the data injection logic checks for marketingCategories. - expect( - bidsConfig.ortb2Fragments.global, - "The global ORTB fragments should remain empty" - ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); - }); + expect(request.url, "The request URL should contain the original URL").to.include( + encodeURIComponent(originalUrl) + ); + }); + }); - it("should append content and user data to existing ORTB fragments", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig = bidsConfiglike(); - // Simulate existing first-party data from another source/module - const existingContentData = { name: "other_content_provider", segment: [{ id: "1" }] }; - const existingUserData = { name: "other_user_provider", segment: [{ id: "2" }] }; + describe("when no stripping options are provided", function () { + it("should send the URL with all query parameters intact", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + const originalUrl = "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + conf.params.websiteToAnalyseUrl = originalUrl; - bidsConfig.ortb2Fragments.global = { - site: { - content: { - data: [existingContentData], - }, - }, - user: { - data: [existingUserData], - }, - }; - const conf = config(); + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; - request.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + expect(request.url, "The request URL should contain the full original URL").to.include( + encodeURIComponent(originalUrl) + ); + }); + }); + }); + }); - const siteData = bidsConfig.ortb2Fragments.global.site.content.data; - const userData = bidsConfig.ortb2Fragments.global.user.data; + // V1 API Format Tests + // These tests use the legacy V1 API response format with marketing_categories structure. + // V1 API uses GET requests and returns: { marketing_categories: { iab_tier_1: [], iab_tier_2: [], etc. } } + // Field names in V1: ID, label, relevance (capital letters) + describe("V1 API", function () { + describe("getBidRequestData", function () { + describe("when using IAB Content Taxonomy 3.0", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponseV1(); + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + expect(request.url, "The request URL should include the product identifier").to.include( + "_neuwo_prod=PrebidModule" + ); + expect(request.url, "V1 API should NOT include iabVersions parameter").to.not.include( + "iabVersions" + ); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should correspond to IAB Content Taxonomy 3.0" + ).to.equal(7); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response (transformed from V1)" + ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); + expect( + contentData.segment[1].id, + "The second segment ID should match the API response (transformed from V1)" + ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].ID); + }); + }); - // Check that the existing data is still there (index 0) - expect(siteData[0], "Existing site.content.data should be preserved").to.deep.equal( - existingContentData - ); - expect(userData[0], "Existing user.data should be preserved").to.deep.equal(existingUserData); + describe("when using IAB Audience Taxonomy 1.1", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponseV1(); + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + const userData = bidsConfig.ortb2Fragments.global.user.data[0]; + expect(userData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + userData.ext.segtax, + "The segtax value should correspond to IAB Audience Taxonomy 1.1" + ).to.equal(4); + expect( + userData.segment[0].id, + "The first segment ID should match the API response (transformed from V1)" + ).to.equal(apiResponse.marketing_categories.iab_audience_tier_3[0].ID); + expect( + userData.segment[1].id, + "The second segment ID should match the API response (transformed from V1)" + ).to.equal(apiResponse.marketing_categories.iab_audience_tier_4[0].ID); + }); + }); - // Check that the new Neuwo data is appended (index 1) - expect(siteData.length, "site.content.data array should have 2 entries").to.equal(2); - expect(userData.length, "user.data array should have 2 entries").to.equal(2); - expect(siteData[1].name, "The appended content data should be from Neuwo").to.equal( - neuwo.DATA_PROVIDER - ); - expect(userData[1].name, "The appended user data should be from Neuwo").to.equal( - neuwo.DATA_PROVIDER - ); + describe("edge cases", function () { + it("should not inject data if 'marketing_categories' is missing from the successful API response", function () { + const apiResponse = { brand_safety: { BS_score: "1.0" } }; // Missing marketing_categories + const bidsConfig = bidsConfiglike(); + const bidsConfigCopy = bidsConfiglike(); + const conf = configV1(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // After a successful response with missing data, the global ortb2 fragments should remain empty + // as the data injection logic checks for marketing_categories in V1 format + expect( + bidsConfig.ortb2Fragments.global, + "The global ORTB fragments should remain empty" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + }); + + it("should handle response with missing marketing_categories", function (done) { + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=no-categories"; + + neuwo.getBidRequestData(bidsConfig, () => { + // Callback should still be called even without marketing_categories + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data; + expect(contentData, "No data should be injected without marketing_categories").to.be.undefined; + done(); + }, conf, "consent data"); + + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify({ brand_safety: { BS_score: "1.0" } }) // Missing marketing_categories + ); + }); + }); }); - }); - describe("getBidRequestData with caching", function () { - describe("when enableCache is true (default)", function () { - it("should cache the API response and reuse it on subsequent calls", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig1 = bidsConfiglike(); - const bidsConfig2 = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=1"; + describe("with iabTaxonomyFilters (client-side filtering)", function () { + it("should work without filtering when no iabTaxonomyFilters provided", function (done) { + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + + neuwo.getBidRequestData( + bidsConfig, + () => { + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + const userData = bidsConfig.ortb2Fragments.global?.user?.data?.[0]; + + expect(contentData, "should have content data").to.exist; + expect(contentData.segment, "should have unfiltered content segments").to.have.lengthOf(2); + expect(userData, "should have user data").to.exist; + expect(userData.segment, "should have unfiltered audience segments").to.have.lengthOf(3); + done(); + }, + conf, + "consent data" + ); - // First call should make an API request - neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); - expect(server.requests.length, "First call should make an API request").to.equal(1); + const request = server.requests[0]; + request.respond(200, {}, JSON.stringify(getNeuwoApiResponseV1())); + }); - const request1 = server.requests[0]; - request1.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) + it("should apply client-side filtering when iabTaxonomyFilters are provided", function (done) { + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + conf.params.iabTaxonomyFilters = { + ContentTier1: { limit: 1, threshold: 0.4 }, + AudienceTier3: { limit: 1, threshold: 0.9 } + }; + + neuwo.getBidRequestData( + bidsConfig, + () => { + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data?.[0]; + const userData = bidsConfig.ortb2Fragments.global?.user?.data?.[0]; + + expect(contentData, "should have content data").to.exist; + expect(contentData.segment, "should have filtered content segments").to.have.lengthOf(2); + + // Check that tier 1 was limited to 1 + const tier1Items = contentData.segment.filter(s => s.id === "274"); + expect(tier1Items, "should have only 1 tier 1 item").to.have.lengthOf(1); + + expect(userData, "should have user data").to.exist; + // Audience tier 3 should be filtered to 1, but tiers 4 and 5 should remain + expect(userData.segment, "should have filtered audience segments").to.have.lengthOf(3); + + done(); + }, + conf, + "consent data" ); - // Second call should use cached response (no new API request) - neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); - expect(server.requests.length, "Second call should not make a new API request").to.equal(1); + const request = server.requests[0]; + request.respond(200, {}, JSON.stringify(getNeuwoApiResponseV1())); + }); + + it("should apply strict client-side filtering that removes all low-relevance items", function (done) { + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + conf.params.iabTaxonomyFilters = { + ContentTier1: { threshold: 0.9 }, // Only keep items with 90%+ relevance + ContentTier2: { threshold: 0.9 }, + AudienceTier4: { threshold: 0.99 }, + AudienceTier5: { threshold: 0.99 } + }; + + neuwo.getBidRequestData( + bidsConfig, + () => { + // Tier 1 has 0.47, Tier 2 has 0.41 - both below 0.9 threshold + // All content segments are filtered out, so content data should not be injected + const contentData = bidsConfig.ortb2Fragments.global?.site?.content?.data; + expect(contentData, "should not inject content data when all segments filtered out").to.be.undefined; + + const userData = bidsConfig.ortb2Fragments.global?.user?.data?.[0]; + expect(userData, "should have user data").to.exist; + // Tier 3 has 0.9923 (passes), Tier 4 has 0.9673 (fails), Tier 5 has 0.9066 (fails) + expect(userData.segment, "should have only 1 audience segment").to.have.lengthOf(1); + + done(); + }, + conf, + "consent data" + ); - // Both configs should have the same data - const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; - const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; - expect(contentData1, "First config should have Neuwo data").to.exist; - expect(contentData2, "Second config should have Neuwo data from cache").to.exist; - expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); - expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + const request = server.requests[0]; + request.respond(200, {}, JSON.stringify(getNeuwoApiResponseV1())); }); - it("should cache when enableCache is explicitly set to true", function () { - const apiResponse = getNeuwoApiResponse(); + it("should handle client-side filtering with cached response", function (done) { const bidsConfig1 = bidsConfiglike(); const bidsConfig2 = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=2"; - conf.params.enableCache = true; + const conf = configV1(); + conf.params.iabTaxonomyFilters = { + ContentTier1: { limit: 1 } + }; + + // First request + neuwo.getBidRequestData( + bidsConfig1, + () => { + // Second request (should use cache) + neuwo.getBidRequestData( + bidsConfig2, + () => { + const contentData = bidsConfig2.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData, "should have content data from cache").to.exist; + expect(contentData.segment, "should apply filtering to cached response").to.have.lengthOf(2); + done(); + }, + conf, + "consent data" + ); + }, + conf, + "consent data" + ); - // First call - neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); - expect(server.requests.length, "First call should make an API request").to.equal(1); + const request = server.requests[0]; + expect(server.requests, "should only make one API request").to.have.lengthOf(1); + request.respond(200, {}, JSON.stringify(getNeuwoApiResponseV1())); + }); + }); - const request1 = server.requests[0]; - request1.respond( + describe("OpenRTB 2.5 category fields (V1 API compatibility)", function () { + it("should not include iabVersions parameter with legacy API", function () { + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + conf.params.enableOrtb25Fields = true; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + + const request = server.requests[0]; + expect(request.url, "should not include iabVersions parameter").to.not.include("iabVersions"); + }); + + it("should not inject category fields with legacy API", function () { + const bidsConfig = bidsConfiglike(); + const conf = configV1(); + conf.params.enableOrtb25Fields = true; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( 200, { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) + JSON.stringify(getNeuwoApiResponseV1()) ); - // Second call should use cache - neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); - expect(server.requests.length, "Second call should use cached response").to.equal(1); + const siteCat = bidsConfig.ortb2Fragments.global?.site?.cat; + expect(siteCat, "should not have site.cat with legacy API").to.be.undefined; }); }); - describe("when enableCache is false", function () { - it("should not cache the API response and make a new request each time", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig1 = bidsConfiglike(); - const bidsConfig2 = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=3"; - conf.params.enableCache = false; + describe("filterIabTaxonomyTier", function () { + it("should return original array when no filter is provided", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" }, + { ID: "3", label: "Category 3", relevance: "0.3" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, {}); + expect(result, "should return all items when no filter is provided").to.have.lengthOf(3); + }); - // First call should make an API request - neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); - expect(server.requests.length, "First call should make an API request").to.equal(1); + it("should return original array when filter is empty", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies); + expect(result, "should return all items when no filter parameter").to.have.lengthOf(2); + }); - const request1 = server.requests[0]; - request1.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + it("should filter by threshold only", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" }, + { ID: "3", label: "Category 3", relevance: "0.3" }, + { ID: "4", label: "Category 4", relevance: "0.1" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { threshold: 0.4 }); + expect(result, "should filter out items below threshold").to.have.lengthOf(2); + expect(result[0].ID, "should keep highest relevance item").to.equal("1"); + expect(result[1].ID, "should keep second highest relevance item").to.equal("2"); + }); - // Second call should make a new API request (not use cache) - neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); - expect(server.requests.length, "Second call should make a new API request").to.equal(2); + it("should limit by count only", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" }, + { ID: "3", label: "Category 3", relevance: "0.3" }, + { ID: "4", label: "Category 4", relevance: "0.1" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { limit: 2 }); + expect(result, "should limit to specified count").to.have.lengthOf(2); + expect(result[0].ID, "should keep highest relevance item").to.equal("1"); + expect(result[1].ID, "should keep second highest relevance item").to.equal("2"); + }); - const request2 = server.requests[1]; - request2.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + it("should return empty array when limit is 0", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { limit: 0 }); + expect(result, "limit 0 should suppress the tier entirely").to.be.an("array").that.is.empty; + }); - // Both configs should have the same data structure - const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; - const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; - expect(contentData1, "First config should have Neuwo data").to.exist; - expect(contentData2, "Second config should have Neuwo data from new request").to.exist; - expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); - expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + it("should apply both threshold and limit", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.9" }, + { ID: "2", label: "Category 2", relevance: "0.7" }, + { ID: "3", label: "Category 3", relevance: "0.6" }, + { ID: "4", label: "Category 4", relevance: "0.5" }, + { ID: "5", label: "Category 5", relevance: "0.2" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { threshold: 0.5, limit: 2 }); + expect(result, "should apply both threshold and limit").to.have.lengthOf(2); + expect(result[0].ID, "should keep highest relevance item").to.equal("1"); + expect(result[1].ID, "should keep second highest relevance item").to.equal("2"); }); - it("should bypass existing cache when enableCache is false", function () { - const apiResponse = getNeuwoApiResponse(); - const bidsConfig1 = bidsConfiglike(); - const bidsConfig2 = bidsConfiglike(); - const bidsConfig3 = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=4"; + it("should preserve original order when filter is empty", function () { + const taxonomies = [ + { ID: "3", label: "Category 3", relevance: "0.3" }, + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, {}); + expect(result[0].ID, "first item should keep original position").to.equal("3"); + expect(result[1].ID, "second item should keep original position").to.equal("1"); + expect(result[2].ID, "third item should keep original position").to.equal("2"); + }); - // First call with caching enabled (default) - neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); - expect(server.requests.length, "First call should make an API request").to.equal(1); + it("should sort by relevance descending only when limit is set", function () { + const taxonomies = [ + { ID: "3", label: "Category 3", relevance: "0.3" }, + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { limit: 2 }); + expect(result).to.have.lengthOf(2); + expect(result[0].ID, "first item should have highest relevance").to.equal("1"); + expect(result[1].ID, "second item should have second highest relevance").to.equal("2"); + }); - const request1 = server.requests[0]; - request1.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + it("should not sort when only threshold is set", function () { + const taxonomies = [ + { ID: "3", label: "Category 3", relevance: "0.6" }, + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.2" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { threshold: 0.5 }); + expect(result).to.have.lengthOf(2); + expect(result[0].ID, "should preserve original order after threshold filter").to.equal("3"); + expect(result[1].ID, "should preserve original order after threshold filter").to.equal("1"); + }); - // Second call with caching enabled should use cache - neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); - expect(server.requests.length, "Second call should use cache").to.equal(1); + it("should handle empty array", function () { + const result = neuwo.filterIabTaxonomyTier([], { threshold: 0.5, limit: 2 }); + expect(result, "should return empty array for empty input").to.be.an("array").that.is.empty; + }); - // Third call with caching disabled should bypass cache - conf.params.enableCache = false; - neuwo.getBidRequestData(bidsConfig3, () => {}, conf, "consent data"); - expect(server.requests.length, "Third call should bypass cache and make new request").to.equal(2); + it("should handle null input", function () { + const result = neuwo.filterIabTaxonomyTier(null, { threshold: 0.5 }); + expect(result, "should return empty array for null input").to.be.an("array").that.is.empty; + }); - const request2 = server.requests[1]; - request2.respond( - 200, - { "Content-Type": "application/json; encoding=UTF-8" }, - JSON.stringify(apiResponse) - ); + it("should handle undefined input", function () { + const result = neuwo.filterIabTaxonomyTier(undefined, { threshold: 0.5 }); + expect(result, "should return empty array for undefined input").to.be.an("array").that.is.empty; + }); + + it("should handle items with missing relevance", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2" }, + { ID: "3", label: "Category 3", relevance: "0.5" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { threshold: 0.3 }); + expect(result, "should handle missing relevance").to.have.lengthOf(2); + }); + + it("should not mutate original array", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2", relevance: "0.5" }, + { ID: "3", label: "Category 3", relevance: "0.3" } + ]; + const original = [...taxonomies]; + neuwo.filterIabTaxonomyTier(taxonomies, { limit: 1 }); + expect(taxonomies, "should not mutate original array").to.deep.equal(original); + }); + + it("should sort items with undefined/null relevance to the end", function () { + const taxonomies = [ + { ID: "1", label: "Category 1", relevance: "0.8" }, + { ID: "2", label: "Category 2" }, // missing relevance + { ID: "3", label: "Category 3", relevance: null }, + { ID: "4", label: "Category 4", relevance: undefined }, + { ID: "5", label: "Category 5", relevance: "0.5" } + ]; + const result = neuwo.filterIabTaxonomyTier(taxonomies, { limit: 5 }); + expect(result, "should return all items").to.have.lengthOf(5); + expect(result[0].ID, "should have highest relevance first").to.equal("1"); + expect(result[1].ID, "should have second highest relevance").to.equal("5"); + // Items with missing/null/undefined relevance should be sorted to the end + const lastThreeIds = [result[2].ID, result[3].ID, result[4].ID].sort(); + expect(lastThreeIds, "items with no relevance should be at the end").to.deep.equal(["2", "3", "4"]); }); }); - }); - describe("getBidRequestData with URL query param stripping", function () { - describe("when stripAllQueryParams is enabled", function () { - it("should strip all query parameters from the analyzed URL", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test&utm_campaign=example&id=5"; - conf.params.stripAllQueryParams = true; + describe("filterIabTaxonomies", function () { + function getTestMarketingCategories() { + return { + iab_tier_1: [ + { ID: "1", label: "Cat 1", relevance: "0.9" }, + { ID: "2", label: "Cat 2", relevance: "0.7" }, + { ID: "3", label: "Cat 3", relevance: "0.5" } + ], + iab_tier_2: [ + { ID: "4", label: "Cat 4", relevance: "0.8" }, + { ID: "5", label: "Cat 5", relevance: "0.6" } + ], + iab_audience_tier_3: [ + { ID: "6", label: "Aud 1", relevance: "0.95" }, + { ID: "7", label: "Aud 2", relevance: "0.85" }, + { ID: "8", label: "Aud 3", relevance: "0.75" } + ] + }; + } + + it("should return original data when no filters provided", function () { + const marketingCategories = getTestMarketingCategories(); + const result = neuwo.filterIabTaxonomies(marketingCategories, {}); + expect(result.iab_tier_1, "should return all tier 1 items").to.have.lengthOf(3); + expect(result.iab_tier_2, "should return all tier 2 items").to.have.lengthOf(2); + expect(result.iab_audience_tier_3, "should return all audience tier 3 items").to.have.lengthOf(3); + }); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + it("should return original data when filters parameter is undefined", function () { + const marketingCategories = getTestMarketingCategories(); + const result = neuwo.filterIabTaxonomies(marketingCategories); + expect(result.iab_tier_1, "should return all tier 1 items").to.have.lengthOf(3); + }); - expect(request.url, "The request URL should not contain encoded query params").to.include( - encodeURIComponent("https://publisher.works/article.php") - ); - expect(request.url, "The request URL should not contain utm_source").to.not.include( - encodeURIComponent("utm_source") - ); + it("should filter ContentTier1 correctly", function () { + const marketingCategories = getTestMarketingCategories(); + const filters = { + ContentTier1: { limit: 1, threshold: 0.8 } + }; + const result = neuwo.filterIabTaxonomies(marketingCategories, filters); + expect(result.iab_tier_1, "should filter tier 1").to.have.lengthOf(1); + expect(result.iab_tier_1[0].ID, "should keep highest relevance item").to.equal("1"); + expect(result.iab_tier_2, "should not filter tier 2").to.have.lengthOf(2); + }); + + it("should filter multiple tiers independently", function () { + const marketingCategories = getTestMarketingCategories(); + const filters = { + ContentTier1: { limit: 2, threshold: 0.6 }, + ContentTier2: { limit: 1, threshold: 0.7 }, + AudienceTier3: { limit: 2, threshold: 0.8 } + }; + const result = neuwo.filterIabTaxonomies(marketingCategories, filters); + expect(result.iab_tier_1, "should filter tier 1 to 2 items").to.have.lengthOf(2); + expect(result.iab_tier_2, "should filter tier 2 to 1 item").to.have.lengthOf(1); + expect(result.iab_tier_2[0].ID, "tier 2 should keep highest item").to.equal("4"); + expect(result.iab_audience_tier_3, "should filter audience tier 3 to 2 items").to.have.lengthOf(2); + }); + + it("should handle tier with no matching config", function () { + const marketingCategories = getTestMarketingCategories(); + const filters = { + ContentTier1: { limit: 1 } + }; + const result = neuwo.filterIabTaxonomies(marketingCategories, filters); + expect(result.iab_tier_1, "should filter configured tier").to.have.lengthOf(1); + expect(result.iab_tier_2, "should keep all items in non-configured tier").to.have.lengthOf(2); + }); + + it("should preserve non-array tier data", function () { + const marketingCategories = { + iab_tier_1: [{ ID: "1", label: "Cat 1", relevance: "0.9" }], + some_other_field: "string value", + another_field: 123 + }; + const filters = { + ContentTier1: { limit: 1 } + }; + const result = neuwo.filterIabTaxonomies(marketingCategories, filters); + expect(result.some_other_field, "should preserve string field").to.equal("string value"); + expect(result.another_field, "should preserve number field").to.equal(123); + }); + + it("should handle null marketingCategories", function () { + const result = neuwo.filterIabTaxonomies(null, { ContentTier1: { limit: 1 } }); + expect(result, "should return null for null input").to.be.null; + }); + + it("should handle undefined marketingCategories", function () { + const result = neuwo.filterIabTaxonomies(undefined, { ContentTier1: { limit: 1 } }); + expect(result, "should return undefined for undefined input").to.be.undefined; + }); + + it("should handle all tier configurations", function () { + const marketingCategories = { + iab_tier_1: [ + { ID: "1", label: "C1", relevance: "0.9" }, + { ID: "2", label: "C2", relevance: "0.5" } + ], + iab_tier_2: [ + { ID: "3", label: "C3", relevance: "0.8" }, + { ID: "4", label: "C4", relevance: "0.4" } + ], + iab_tier_3: [ + { ID: "5", label: "C5", relevance: "0.7" }, + { ID: "6", label: "C6", relevance: "0.3" } + ], + iab_audience_tier_3: [ + { ID: "7", label: "A1", relevance: "0.95" }, + { ID: "8", label: "A2", relevance: "0.45" } + ], + iab_audience_tier_4: [ + { ID: "9", label: "A3", relevance: "0.85" }, + { ID: "10", label: "A4", relevance: "0.35" } + ], + iab_audience_tier_5: [ + { ID: "11", label: "A5", relevance: "0.75" }, + { ID: "12", label: "A6", relevance: "0.25" } + ] + }; + const filters = { + ContentTier1: { limit: 1, threshold: 0.8 }, + ContentTier2: { limit: 1, threshold: 0.7 }, + ContentTier3: { limit: 1, threshold: 0.6 }, + AudienceTier3: { limit: 1, threshold: 0.9 }, + AudienceTier4: { limit: 1, threshold: 0.8 }, + AudienceTier5: { limit: 1, threshold: 0.7 } + }; + const result = neuwo.filterIabTaxonomies(marketingCategories, filters); + expect(result.iab_tier_1, "ContentTier1 filtered").to.have.lengthOf(1); + expect(result.iab_tier_2, "ContentTier2 filtered").to.have.lengthOf(1); + expect(result.iab_tier_3, "ContentTier3 filtered").to.have.lengthOf(1); + expect(result.iab_audience_tier_3, "AudienceTier3 filtered").to.have.lengthOf(1); + expect(result.iab_audience_tier_4, "AudienceTier4 filtered").to.have.lengthOf(1); + expect(result.iab_audience_tier_5, "AudienceTier5 filtered").to.have.lengthOf(1); }); }); - describe("when stripQueryParamsForDomains is enabled", function () { - it("should strip query params only for matching domains", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?foo=bar&id=5"; - conf.params.stripQueryParamsForDomains = ["publisher.works"]; + describe("transformSegmentsV1ToV2", function () { + it("should transform V1 segment format to V2 format", function () { + const v1Segments = [ + { ID: "274", label: "Home & Garden", relevance: "0.47" }, + { ID: "216", label: "Cooking", relevance: "0.41" } + ]; + const result = neuwo.transformSegmentsV1ToV2(v1Segments); + + expect(result, "should return array with same length").to.have.lengthOf(2); + expect(result[0], "first segment should have id property").to.have.property("id", "274"); + expect(result[0], "first segment should have name property").to.have.property("name", "Home & Garden"); + expect(result[0], "first segment should have relevance property").to.have.property("relevance", "0.47"); + expect(result[1], "second segment should have id property").to.have.property("id", "216"); + expect(result[1], "second segment should have name property").to.have.property("name", "Cooking"); + expect(result[1], "second segment should have relevance property").to.have.property("relevance", "0.41"); + }); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + it("should handle empty array", function () { + const result = neuwo.transformSegmentsV1ToV2([]); + expect(result, "should return empty array").to.be.an("array").that.is.empty; + }); - expect(request.url, "The request URL should contain the URL without query params").to.include( - encodeURIComponent("https://publisher.works/article.php") - ); - expect(request.url, "The request URL should not contain the id param").to.not.include( - encodeURIComponent("id=5") - ); + it("should handle null input", function () { + const result = neuwo.transformSegmentsV1ToV2(null); + expect(result, "should return empty array for null").to.be.an("array").that.is.empty; }); - it("should not strip query params for non-matching domains", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://other-domain.com/page?foo=bar&id=5"; - conf.params.stripQueryParamsForDomains = ["publisher.works"]; + it("should handle undefined input", function () { + const result = neuwo.transformSegmentsV1ToV2(undefined); + expect(result, "should return empty array for undefined").to.be.an("array").that.is.empty; + }); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + it("should handle non-array input", function () { + const result = neuwo.transformSegmentsV1ToV2("not an array"); + expect(result, "should return empty array for non-array").to.be.an("array").that.is.empty; + }); - expect(request.url, "The request URL should contain the full URL with query params").to.include( - encodeURIComponent("https://other-domain.com/page?foo=bar&id=5") - ); + it("should handle segments with missing properties", function () { + const v1Segments = [ + { ID: "274", label: "Home & Garden" }, // missing relevance + { ID: "216", relevance: "0.41" }, // missing label + { label: "Test", relevance: "0.5" } // missing ID + ]; + const result = neuwo.transformSegmentsV1ToV2(v1Segments); + + expect(result, "should return array with same length").to.have.lengthOf(3); + expect(result[0].id, "should handle missing relevance").to.equal("274"); + expect(result[0].relevance, "should set undefined for missing relevance").to.be.undefined; + expect(result[1].name, "should set undefined for missing label").to.be.undefined; + expect(result[2].id, "should set undefined for missing ID").to.be.undefined; }); - it("should handle subdomain matching correctly", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://sub.publisher.works/page?tracking=123"; - conf.params.stripQueryParamsForDomains = ["publisher.works"]; + it("should preserve all properties from V1 format", function () { + const v1Segments = [ + { ID: "49", label: "Female", relevance: "0.9923" } + ]; + const result = neuwo.transformSegmentsV1ToV2(v1Segments); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + expect(result[0], "should only have id, name, and relevance properties").to.have.all.keys("id", "name", "relevance"); + }); + }); - expect(request.url, "The request URL should strip params for subdomain").to.include( - encodeURIComponent("https://sub.publisher.works/page") - ); - expect(request.url, "The request URL should not contain tracking param").to.not.include( - encodeURIComponent("tracking=123") - ); + describe("transformV1ResponseToV2", function () { + it("should transform complete V1 response to V2 format", function () { + const v1Response = { + marketing_categories: { + iab_tier_1: [{ ID: "274", label: "Home & Garden", relevance: "0.47" }], + iab_tier_2: [{ ID: "216", label: "Cooking", relevance: "0.41" }], + iab_tier_3: [{ ID: "388", label: "Recipes", relevance: "0.35" }], + iab_audience_tier_3: [{ ID: "49", label: "Female", relevance: "0.9923" }], + iab_audience_tier_4: [{ ID: "431", label: "Age 25-34", relevance: "0.9673" }], + iab_audience_tier_5: [{ ID: "98", label: "Interest: Cooking", relevance: "0.9066" }] + } + }; + const contentSegtax = 7; // IAB Content Taxonomy 3.0 + const result = neuwo.transformV1ResponseToV2(v1Response, contentSegtax); + + expect(result, "should have content segtax key").to.have.property("7"); + expect(result, "should have audience segtax key").to.have.property("4"); + expect(result["7"], "content segtax should have all tiers").to.have.all.keys("1", "2", "3"); + expect(result["4"], "audience segtax should have all tiers").to.have.all.keys("3", "4", "5"); + expect(result["7"]["1"][0], "content tier 1 should be transformed").to.deep.equal({ + id: "274", + name: "Home & Garden", + relevance: "0.47" + }); + expect(result["4"]["3"][0], "audience tier 3 should be transformed").to.deep.equal({ + id: "49", + name: "Female", + relevance: "0.9923" + }); + }); + + it("should handle different content segtax values", function () { + const v1Response = { + marketing_categories: { + iab_tier_1: [{ ID: "274", label: "Home & Garden", relevance: "0.47" }] + } + }; + + // Test with segtax 6 (IAB 2.2) + const result6 = neuwo.transformV1ResponseToV2(v1Response, 6); + expect(result6, "should use segtax 6 for content").to.have.property("6"); + expect(result6["6"], "should have tier 1").to.have.property("1"); + + // Test with segtax 1 (IAB 1.0) + const result1 = neuwo.transformV1ResponseToV2(v1Response, 1); + expect(result1, "should use segtax 1 for content").to.have.property("1"); + expect(result1["1"], "should have tier 1").to.have.property("1"); + }); + + it("should handle missing marketing_categories", function () { + const v1Response = {}; + const result = neuwo.transformV1ResponseToV2(v1Response, 6); + + expect(result, "should have content segtax key").to.have.property("6"); + expect(result, "should have audience segtax key").to.have.property("4"); + expect(result["6"], "content segtax should be empty object").to.deep.equal({}); + expect(result["4"], "audience segtax should be empty object").to.deep.equal({}); + }); + + it("should handle null v1Response", function () { + const result = neuwo.transformV1ResponseToV2(null, 6); + + expect(result, "should have content segtax key").to.have.property("6"); + expect(result, "should have audience segtax key").to.have.property("4"); + expect(result["6"], "content segtax should be empty object").to.deep.equal({}); + expect(result["4"], "audience segtax should be empty object").to.deep.equal({}); + }); + + it("should handle undefined v1Response", function () { + const result = neuwo.transformV1ResponseToV2(undefined, 6); + + expect(result, "should have content segtax key").to.have.property("6"); + expect(result, "should have audience segtax key").to.have.property("4"); + expect(result["6"], "content segtax should be empty object").to.deep.equal({}); + expect(result["4"], "audience segtax should be empty object").to.deep.equal({}); + }); + + it("should handle partial V1 response with only content tiers", function () { + const v1Response = { + marketing_categories: { + iab_tier_1: [{ ID: "274", label: "Home & Garden", relevance: "0.47" }], + iab_tier_2: [{ ID: "216", label: "Cooking", relevance: "0.41" }] + // No tier 3, no audience tiers + } + }; + const result = neuwo.transformV1ResponseToV2(v1Response, 6); + + expect(result["6"], "should have only tier 1 and 2").to.have.all.keys("1", "2"); + expect(result["6"], "should not have tier 3").to.not.have.property("3"); + expect(result["4"], "audience segtax should be empty").to.deep.equal({}); + }); + + it("should handle partial V1 response with only audience tiers", function () { + const v1Response = { + marketing_categories: { + iab_audience_tier_3: [{ ID: "49", label: "Female", relevance: "0.9923" }], + iab_audience_tier_4: [{ ID: "431", label: "Age 25-34", relevance: "0.9673" }] + // No content tiers + } + }; + const result = neuwo.transformV1ResponseToV2(v1Response, 6); + + expect(result["6"], "content segtax should be empty").to.deep.equal({}); + expect(result["4"], "should have tier 3 and 4").to.have.all.keys("3", "4"); + expect(result["4"], "should not have tier 5").to.not.have.property("5"); + }); + + it("should handle empty arrays in V1 response", function () { + const v1Response = { + marketing_categories: { + iab_tier_1: [], + iab_tier_2: [], + iab_audience_tier_3: [] + } + }; + const result = neuwo.transformV1ResponseToV2(v1Response, 6); + + expect(result["6"]["1"], "content tier 1 should be empty array").to.be.an("array").that.is.empty; + expect(result["6"]["2"], "content tier 2 should be empty array").to.be.an("array").that.is.empty; + expect(result["4"]["3"], "audience tier 3 should be empty array").to.be.an("array").that.is.empty; + }); + + it("should convert segtax to string keys", function () { + const v1Response = { + marketing_categories: { + iab_tier_1: [{ ID: "274", label: "Home & Garden", relevance: "0.47" }] + } + }; + const result = neuwo.transformV1ResponseToV2(v1Response, 6); + + expect(Object.keys(result), "segtax keys should be strings").to.include.members(["6", "4"]); + expect(typeof Object.keys(result)[0], "key type should be string").to.equal("string"); + }); + + it("should preserve brand_safety and other non-marketing_categories fields", function () { + const v1Response = { + brand_safety: { BS_score: "1.0" }, + marketing_categories: { + iab_tier_1: [{ ID: "274", label: "Home & Garden", relevance: "0.47" }] + }, + custom_field: "value" + }; + const result = neuwo.transformV1ResponseToV2(v1Response, 6); + + expect(result, "should not have brand_safety").to.not.have.property("brand_safety"); + expect(result, "should not have custom_field").to.not.have.property("custom_field"); + expect(result, "should only have segtax keys").to.have.all.keys("6", "4"); }); }); - describe("when stripQueryParams is enabled", function () { - it("should strip only specified query parameters", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test&utm_campaign=example&id=5"; - conf.params.stripQueryParams = ["utm_source", "utm_campaign"]; + describe("getBidRequestData with caching (legacy cache key isolation)", function () { + beforeEach(function () { + neuwo.clearCache(); + }); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + it("should not share cache between different iabContentTaxonomyVersion values", function () { + const apiResponse = getNeuwoApiResponseV1(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf1 = configV1(); + conf1.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf1.params.enableCache = true; + conf1.params.iabContentTaxonomyVersion = "3.0"; + + const conf2 = configV1(); + conf2.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf2.params.enableCache = true; + conf2.params.iabContentTaxonomyVersion = "2.2"; + + // First call with taxonomy 3.0 + neuwo.getBidRequestData(bidsConfig1, () => {}, conf1, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); - expect(request.url, "The request URL should contain the id param").to.include( - encodeURIComponent("id=5") - ); - expect(request.url, "The request URL should not contain utm_source").to.not.include( - encodeURIComponent("utm_source") - ); - expect(request.url, "The request URL should not contain utm_campaign").to.not.include( - encodeURIComponent("utm_campaign") + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) ); - }); - it("should handle stripping params that result in no query string", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test"; - conf.params.stripQueryParams = ["utm_source"]; + // Verify first call has content under segtax 7 (taxonomy 3.0) + const contentData1 = bidsConfig1.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData1, "First call should have content data").to.exist; + expect(contentData1.ext.segtax, "First call should use segtax 7").to.equal(7); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + // Second call with taxonomy 2.2 - should NOT use cache from taxonomy 3.0 + neuwo.getBidRequestData(bidsConfig2, () => {}, conf2, "consent data"); + expect(server.requests.length, "Second call should make a new API request for different taxonomy").to.equal(2); - expect(request.url, "The request URL should not contain a query string").to.include( - encodeURIComponent("https://publisher.works/article.php") + server.requests[1].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) ); - expect(request.url, "The request URL should not contain utm_source").to.not.include( - encodeURIComponent("utm_source") + + // Verify second call has content under segtax 6 (taxonomy 2.2) + const contentData2 = bidsConfig2.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData2, "Second call should have content data").to.exist; + expect(contentData2.ext.segtax, "Second call should use segtax 6").to.equal(6); + }); + + it("should not share cache between different iabTaxonomyFilters values", function () { + const apiResponse = getNeuwoApiResponseV1(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf1 = configV1(); + conf1.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf1.params.enableCache = true; + conf1.params.iabTaxonomyFilters = { ContentTier1: { limit: 1 } }; + + const conf2 = configV1(); + conf2.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf2.params.enableCache = true; + // No filters + + // First call with filters + neuwo.getBidRequestData(bidsConfig1, () => {}, conf1, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) ); + + // Second call without filters - should NOT reuse filtered cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf2, "consent data"); + expect(server.requests.length, "Second call should make a new API request for different filters").to.equal(2); }); - it("should leave URL unchanged if specified params do not exist", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - const originalUrl = "https://publisher.works/article.php?id=5"; - conf.params.websiteToAnalyseUrl = originalUrl; - conf.params.stripQueryParams = ["utm_source", "nonexistent"]; + it("should share cache when legacy config is identical", function () { + const apiResponse = getNeuwoApiResponseV1(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = configV1(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf.params.enableCache = true; - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + // First call + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); - expect(request.url, "The request URL should contain the original URL").to.include( - encodeURIComponent(originalUrl) + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) ); - }); - }); - describe("when no stripping options are provided", function () { - it("should send the URL with all query parameters intact", function () { - const bidsConfig = bidsConfiglike(); - const conf = config(); - const originalUrl = "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; - conf.params.websiteToAnalyseUrl = originalUrl; + // Second call with same config - should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should use cache for identical config").to.equal(1); - neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); - const request = server.requests[0]; + const contentData1 = bidsConfig1.ortb2Fragments.global?.site?.content?.data?.[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData1, "First call should have content data").to.exist; + expect(contentData2, "Second call should have content data from cache").to.exist; + expect(contentData1.segment, "Cached data should match original").to.deep.equal(contentData2.segment); + }); - expect(request.url, "The request URL should contain the full original URL").to.include( - encodeURIComponent(originalUrl) + it("should not share pending requests between different taxonomy versions", function (done) { + const apiResponse = getNeuwoApiResponseV1(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf1 = configV1(); + conf1.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf1.params.enableCache = true; + conf1.params.iabContentTaxonomyVersion = "3.0"; + + const conf2 = configV1(); + conf2.params.websiteToAnalyseUrl = "https://publisher.works/same-page"; + conf2.params.enableCache = true; + conf2.params.iabContentTaxonomyVersion = "2.2"; + + let callbackCount = 0; + const callback = () => { + callbackCount++; + if (callbackCount === 2) { + try { + // Both should have made separate API requests + expect(server.requests.length, "Should make two API requests for different taxonomies").to.equal(2); + + const contentData1 = bidsConfig1.ortb2Fragments.global?.site?.content?.data?.[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global?.site?.content?.data?.[0]; + expect(contentData1, "First call should have content data").to.exist; + expect(contentData2, "Second call should have content data").to.exist; + expect(contentData1.ext.segtax, "First call should use segtax 7").to.equal(7); + expect(contentData2.ext.segtax, "Second call should use segtax 6").to.equal(6); + done(); + } catch (e) { + done(e); + } + } + }; + + // Two concurrent calls with different taxonomy versions + neuwo.getBidRequestData(bidsConfig1, callback, conf1, "consent data"); + neuwo.getBidRequestData(bidsConfig2, callback, conf2, "consent data"); + + // Should be two separate requests, not shared pending promise + expect(server.requests.length, "Should make two separate API requests").to.equal(2); + + server.requests[0].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + server.requests[1].respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) ); }); }); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 9834f27f132..e05d797526a 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -18,8 +18,8 @@ describe('nextMillenniumBidAdapterTests', () => { { title: 'imp - banner', data: { + impId: '5', id: '123', - postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-banner-1', @@ -37,7 +37,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'e36ea395f67f', + id: '5', bidfloorcur: 'EUR', bidfloor: 1.11, ext: {prebid: {storedrequest: {id: '123'}}}, @@ -53,8 +53,8 @@ describe('nextMillenniumBidAdapterTests', () => { { title: 'imp - video', data: { + impId: '3', id: '234', - postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}}, adUnitCode: 'test-video-1', @@ -71,7 +71,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'e36ea395f67f', + id: '3', bidfloorcur: 'USD', ext: {prebid: {storedrequest: {id: '234'}}}, video: { @@ -89,8 +89,8 @@ describe('nextMillenniumBidAdapterTests', () => { { title: 'imp - mediaTypes.video is empty', data: { + impId: '4', id: '234', - postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {w: 640, h: 480}}, bidId: 'e36ea395f67f', @@ -105,7 +105,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'e36ea395f67f', + id: '4', bidfloorcur: 'USD', ext: {prebid: {storedrequest: {id: '234'}}}, video: {w: 640, h: 480, mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript']}, @@ -115,8 +115,8 @@ describe('nextMillenniumBidAdapterTests', () => { { title: 'imp with gpid', data: { + impId: '2', id: '123', - postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-gpid-1', @@ -132,7 +132,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'e36ea395f67a', + id: '2', ext: { prebid: {storedrequest: {id: '123'}}, gpid: 'imp-gpid-123' @@ -144,8 +144,8 @@ describe('nextMillenniumBidAdapterTests', () => { { title: 'imp with pbadslot', data: { + impId: '1', id: '123', - postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-gpid-1', @@ -167,7 +167,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'e36ea395f67a', + id: '1', ext: { prebid: {storedrequest: {id: '123'}}, }, @@ -178,8 +178,8 @@ describe('nextMillenniumBidAdapterTests', () => { for (const {title, data, expected} of dataTests) { it(title, () => { - const {bid, id, mediaTypes, postBody} = data; - const imp = getImp(bid, id, mediaTypes, postBody); + const {impId, bid, id, mediaTypes} = data; + const imp = getImp(impId, bid, id, mediaTypes); expect(imp).to.deep.equal(expected); }); } @@ -900,17 +900,17 @@ describe('nextMillenniumBidAdapterTests', () => { describe('Check ext.next_mil_imps', function() { const expectedNextMilImps = [ { - impId: 'bid1234', + impId: '1', nextMillennium: {refresh_count: 1}, }, { - impId: 'bid1235', + impId: '2', nextMillennium: {refresh_count: 1}, }, { - impId: 'bid1236', + impId: '3', nextMillennium: {refresh_count: 1}, }, ]; @@ -1183,6 +1183,12 @@ describe('nextMillenniumBidAdapterTests', () => { expect(requestData.id).to.equal(expected.id); expect(requestData.tmax).to.equal(expected.tmax); expect(requestData?.imp?.length).to.equal(expected.impSize); + + for (let i = 0; i < bidRequests.length; i++) { + const impId = String(i + 1); + expect(impId).to.equal(requestData.imp[i].id); + expect(bidRequests[i].bidId).to.equal(request[0].bidIds.get(impId)); + }; }); }; }); @@ -1199,7 +1205,7 @@ describe('nextMillenniumBidAdapterTests', () => { bid: [ { id: '7457329903666272789-0', - impid: '700ce0a43f72', + impid: '1', price: 0.5, adm: 'Hello! It\'s a test ad!', adid: '96846035-0', @@ -1210,7 +1216,7 @@ describe('nextMillenniumBidAdapterTests', () => { { id: '7457329903666272789-1', - impid: '700ce0a43f73', + impid: '2', price: 0.7, adm: 'https://some_vast_host.com/vast.xml', adid: '96846035-1', @@ -1222,7 +1228,7 @@ describe('nextMillenniumBidAdapterTests', () => { { id: '7457329903666272789-2', - impid: '700ce0a43f74', + impid: '3', price: 1.0, adm: '', adid: '96846035-3', @@ -1238,6 +1244,14 @@ describe('nextMillenniumBidAdapterTests', () => { }, }, + bidRequest: { + bidIds: new Map([ + ['1', '700ce0a43f72'], + ['2', '700ce0a43f73'], + ['3', '700ce0a43f74'], + ]), + }, + expected: [ { title: 'banner', @@ -1308,15 +1322,19 @@ describe('nextMillenniumBidAdapterTests', () => { const tests = [ { title: 'parameters adSlots and allowedAds are empty', + impId: '1', bid: { params: {}, }, - expected: {}, + expected: { + impId: '1', + }, }, { title: 'parameters adSlots and allowedAds', + impId: '2', bid: { params: { adSlots: ['test1'], @@ -1325,15 +1343,17 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { + impId: '2', adSlots: ['test1'], allowedAds: ['test2'], }, }, ]; - for (const {title, bid, expected} of tests) { + for (const {title, impId, bid, expected} of tests) { it(title, () => { - const extNextMilImp = getExtNextMilImp(bid); + const extNextMilImp = getExtNextMilImp(impId, bid); + expect(extNextMilImp.impId).to.deep.equal(expected.impId); expect(extNextMilImp.nextMillennium.adSlots).to.deep.equal(expected.adSlots); expect(extNextMilImp.nextMillennium.allowedAds).to.deep.equal(expected.allowedAds); }); diff --git a/test/spec/modules/nodalsAiRtdProvider_spec.js b/test/spec/modules/nodalsAiRtdProvider_spec.js index 486822f3934..9155e07b2ff 100644 --- a/test/spec/modules/nodalsAiRtdProvider_spec.js +++ b/test/spec/modules/nodalsAiRtdProvider_spec.js @@ -965,4 +965,62 @@ describe('NodalsAI RTD Provider', () => { expect(server.requests.length).to.equal(0); }); }); + + describe('#getEngine()', () => { + it('should return undefined when $nodals object does not exist', () => { + // Setup data in storage to avoid triggering fetchData + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + delete window.$nodals; + const result = nodalsAiRtdSubmodule.getTargetingData([], validConfig, permissiveUserConsent); + expect(result).to.deep.equal({}); + }); + + it('should return undefined when adTargetingEngine object does not exist', () => { + // Setup data in storage to avoid triggering fetchData + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + window.$nodals = {}; + const result = nodalsAiRtdSubmodule.getTargetingData([], validConfig, permissiveUserConsent); + expect(result).to.deep.equal({}); + }); + + it('should return undefined when specific engine version does not exist', () => { + // Setup data in storage to avoid triggering fetchData + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + window.$nodals = { + adTargetingEngine: {} + }; + const result = nodalsAiRtdSubmodule.getTargetingData([], validConfig, permissiveUserConsent); + expect(result).to.deep.equal({}); + }); + + it('should return undefined when property access throws an error', () => { + // Setup data in storage to avoid triggering fetchData + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + Object.defineProperty(window, '$nodals', { + get() { + throw new Error('Access denied'); + }, + configurable: true + }); + const result = nodalsAiRtdSubmodule.getTargetingData([], validConfig, permissiveUserConsent); + expect(result).to.deep.equal({}); + delete window.$nodals; + }); + }); }); diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index f6922f70942..3cd7542f6c2 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -3,6 +3,7 @@ import sinon from 'sinon'; import { spec, ortbConverterProps } from 'modules/oguryBidAdapter'; import * as utils from 'src/utils.js'; import { server } from '../../mocks/xhr.js'; +import {getDevicePixelRatio} from '../../../libraries/devicePixelRatio/devicePixelRatio.js'; const BID_URL = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_URL = 'https://ms-ads-monitoring-events.presage.io/bid_timeout' @@ -121,34 +122,34 @@ describe('OguryBidAdapter', () => { describe('isBidRequestValid', () => { it('should validate correct bid', () => { - const validBid = utils.deepClone(bidRequests[0]); + let validBid = utils.deepClone(bidRequests[0]); - const isValid = spec.isBidRequestValid(validBid); + let isValid = spec.isBidRequestValid(validBid); expect(isValid).to.true; }); it('should not validate when sizes is not defined', () => { - const invalidBid = utils.deepClone(bidRequests[0]); + let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.sizes; delete invalidBid.mediaTypes; - const isValid = spec.isBidRequestValid(invalidBid); + let isValid = spec.isBidRequestValid(invalidBid); expect(isValid).to.be.false; }); it('should not validate bid when adunit is not defined', () => { - const invalidBid = utils.deepClone(bidRequests[0]); + let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.params.adUnitId; - const isValid = spec.isBidRequestValid(invalidBid); + let isValid = spec.isBidRequestValid(invalidBid); expect(isValid).to.to.be.false; }); it('should not validate bid when assetKey is not defined', () => { - const invalidBid = utils.deepClone(bidRequests[0]); + let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.params.assetKey; - const isValid = spec.isBidRequestValid(invalidBid); + let isValid = spec.isBidRequestValid(invalidBid); expect(isValid).to.be.false; }); @@ -201,16 +202,12 @@ describe('OguryBidAdapter', () => { syncOptions = { pixelEnabled: true }; }); - it('should return syncs array with three elements of type image', () => { + it('should return syncs array with one element of type image', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.contain('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch'); - expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.contain('https://ms-cookie-sync.presage.io/ttd/init-sync'); - expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.contain('https://ms-cookie-sync.presage.io/xandr/init-sync'); + expect(userSyncs[0].url).to.contain('https://ms-cookie-sync.presage.io/user-sync'); }); it('should set the source as query param', () => { @@ -220,23 +217,17 @@ describe('OguryBidAdapter', () => { it('should set the tcString as query param', () => { 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) + 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) - 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', () => { @@ -251,13 +242,9 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal('') }); it('should return syncs array with three elements of type image when consentString is null', () => { @@ -267,39 +254,27 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).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, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).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, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal('') }); it('should return syncs array with three elements of type image when gdprConsent is null and gdprApplies is false', () => { @@ -309,13 +284,9 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal('') }); it('should return syncs array with three elements of type image when gdprConsent is empty string and gdprApplies is false', () => { @@ -325,13 +296,9 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal('') }); it('should return syncs array with three elements of type image when gppString is undefined', () => { @@ -341,22 +308,12 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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', () => { @@ -366,66 +323,36 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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).to.have.lengthOf(1); 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).to.have.lengthOf(1); 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', () => { @@ -435,22 +362,12 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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', () => { @@ -460,22 +377,12 @@ describe('OguryBidAdapter', () => { }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs).to.have.lengthOf(1); 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('') }); }); @@ -671,10 +578,6 @@ describe('OguryBidAdapter', () => { return stubbedCurrentTime; }); - const stubbedDevicePixelMethod = sinon.stub(window, 'devicePixelRatio').get(function() { - return stubbedDevicePixelRatio; - }); - const defaultTimeout = 1000; function assertImpObject(ortbBidRequest, bidRequest) { @@ -719,7 +622,7 @@ describe('OguryBidAdapter', () => { expect(dataRequest.ext).to.deep.equal({ prebidversion: '$prebid.version$', - adapterversion: '2.0.4' + adapterversion: '2.0.5' }); expect(dataRequest.device).to.deep.equal({ @@ -733,7 +636,7 @@ describe('OguryBidAdapter', () => { beforeEach(() => { windowTopStub = sinon.stub(utils, 'getWindowTop'); - windowTopStub.returns({ location: { href: currentLocation } }); + windowTopStub.returns({ location: { href: currentLocation }, devicePixelRatio: stubbedDevicePixelRatio}); }); afterEach(() => { @@ -742,7 +645,6 @@ describe('OguryBidAdapter', () => { after(() => { stubbedCurrentTimeMethod.restore(); - stubbedDevicePixelMethod.restore(); }); it('sends bid request to ENDPOINT via POST', function () { @@ -851,7 +753,7 @@ describe('OguryBidAdapter', () => { }); describe('interpretResponse', function () { - const openRtbBidResponse = { + let openRtbBidResponse = { body: { id: 'id_of_bid_response', seatbid: [{ diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index 2c36465c66d..da9c21e7ae3 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -34,7 +34,11 @@ describe('omsBidAdapter', function () { }; win = { document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600, + } }, location: { href: "http:/location" @@ -80,6 +84,7 @@ describe('omsBidAdapter', function () { sandbox = sinon.createSandbox(); sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(winDimensions, 'getWinDimensions').returns(win); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); @@ -262,6 +267,16 @@ describe('omsBidAdapter', function () { expect(data.regs.coppa).to.equal(1); }); + it('sends instl property when ortb2Imp.instl = 1', function () { + const data = JSON.parse(spec.buildRequests([{ ...bidRequests[0], ortb2Imp: { instl: 1 }}]).data); + expect(data.imp[0].instl).to.equal(1); + }); + + it('ignores instl property when ortb2Imp.instl is falsy', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.imp[0].instl).to.be.undefined; + }); + it('sends schain', function () { const data = JSON.parse(spec.buildRequests(bidRequests).data); expect(data).to.not.be.undefined; @@ -347,8 +362,6 @@ describe('omsBidAdapter', function () { context('when element is partially in view', function () { it('returns percentage', function () { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, {width: 800, height: 800}); const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -358,8 +371,6 @@ describe('omsBidAdapter', function () { context('when width or height of the element is zero', function () { it('try to use alternative values', function () { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, {width: 0, height: 0}); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); @@ -451,7 +462,7 @@ describe('omsBidAdapter', function () { 'currency': 'USD', 'netRevenue': true, 'mediaType': 'video', - 'ad': `
`, + 'vastXml': ``, 'ttl': 300, 'meta': { 'advertiserDomains': ['example.com'] diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index aa953e35be5..dd37a929b45 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -491,7 +491,7 @@ describe('onetag', function () { }); it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version', 'fledgeEnabled'); + expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version', 'fledgeEnabled'); expect(data.location).to.satisfy(function (value) { return value === null || typeof value === 'string'; }); @@ -502,8 +502,6 @@ describe('onetag', function () { expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); expect(data.wHeight).to.be.a('number'); - expect(data.sLeft).to.be.a('number'); - expect(data.sTop).to.be.a('number'); expect(data.hLength).to.be.a('number'); expect(data.networkConnectionType).to.satisfy(function (value) { return value === null || typeof value === 'string' @@ -1133,8 +1131,6 @@ function getBannerVideoRequest() { wHeight: 949, sWidth: 1920, sHeight: 1080, - sLeft: 1987, - sTop: 27, xOffset: 0, yOffset: 0, docHidden: false, diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index 6b4d44f49ed..d043363b34d 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -34,9 +34,12 @@ describe('onomagicBidAdapter', function() { }; win = { document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600 + } }, - innerWidth: 800, innerHeight: 600 }; @@ -57,6 +60,7 @@ describe('onomagicBidAdapter', function() { }]; sandbox = sinon.createSandbox(); + sandbox.stub(winDimensions, 'getWinDimensions').returns(win); sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); @@ -162,8 +166,6 @@ describe('onomagicBidAdapter', function() { context('when element is partially in view', function() { it('returns percentage', function() { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, { width: 800, height: 800 }); const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -173,8 +175,6 @@ describe('onomagicBidAdapter', function() { context('when width or height of the element is zero', function() { it('try to use alternative values', function() { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); diff --git a/test/spec/modules/operaadsBidAdapter_spec.js b/test/spec/modules/operaadsBidAdapter_spec.js index 15708c1bb42..0ff16d72293 100644 --- a/test/spec/modules/operaadsBidAdapter_spec.js +++ b/test/spec/modules/operaadsBidAdapter_spec.js @@ -217,7 +217,7 @@ describe('Opera Ads Bid Adapter', function () { const bidRequest = bidRequests[i]; expect(req.method).to.equal('POST'); - expect(req.url).to.equal('https://s.adx.opera.com/ortb/v2/' + + expect(req.url).to.equal('https://s.oa.opera.com/ortb/v2/' + bidRequest.params.publisherId + '?ep=' + bidRequest.params.endpointId); expect(req.options).to.be.an('object'); @@ -546,8 +546,8 @@ describe('Opera Ads Bid Adapter', function () { 'id': '003004d9c05c6bc7fec0', 'impid': '22c4871113f461', 'price': 1.04, - 'nurl': 'https://s.adx.opera.com/win', - 'lurl': 'https://s.adx.opera.com/loss', + 'nurl': 'https://s.oa.opera.com/win', + 'lurl': 'https://s.oa.opera.com/loss', 'adm': '', 'adomain': [ 'opera.com', @@ -628,8 +628,8 @@ describe('Opera Ads Bid Adapter', function () { 'id': '003004d9c05c6bc7fec0', 'impid': '22c4871113f461', 'price': 1.04, - 'nurl': 'https://s.adx.opera.com/win', - 'lurl': 'https://s.adx.opera.com/loss', + 'nurl': 'https://s.oa.opera.com/win', + 'lurl': 'https://s.oa.opera.com/loss', 'adm': 'Static VAST TemplateStatic VAST Taghttp://example.com/pixel.gif?asi=[ADSERVINGID]00:00:08http://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://example.com/pixel.gifhttp://www.jwplayer.com/http://example.com/pixel.gif?r=[REGULATIONS]&gdpr=[GDPRCONSENT]&pu=[PAGEURL]&da=[DEVICEUA] http://example.com/uploads/myPrerollVideo.mp4 https://example.com/adchoices-sm.pnghttps://sample-url.com', 'adomain': [ 'opera.com', @@ -698,8 +698,8 @@ describe('Opera Ads Bid Adapter', function () { 'id': '003004d9c05c6bc7fec0', 'impid': '22c4871113f461', 'price': 1.04, - 'nurl': 'https://s.adx.opera.com/win', - 'lurl': 'https://s.adx.opera.com/loss', + 'nurl': 'https://s.oa.opera.com/win', + 'lurl': 'https://s.oa.opera.com/loss', 'adm': '{"native":{"ver":"1.1","assets":[{"id":1,"required":1,"title":{"text":"The first personal browser"}},{"id":2,"required":1,"img":{"url":"https://res.adx.opera.com/xxx.png","w":720,"h":1280}},{"id":3,"required":1,"img":{"url":"https://res.adx.opera.com/xxx.png","w":60,"h":60}},{"id":4,"required":1,"data":{"value":"Download Opera","len":14}},{"id":5,"required":1,"data":{"value":"Opera","len":5}},{"id":6,"required":1,"data":{"value":"Download","len":8}}],"link":{"url":"https://www.opera.com/mobile/opera","clicktrackers":["https://thirdpart-click.tracker.com","https://t-odx.op-mobile.opera.com/click"]},"imptrackers":["https://thirdpart-imp.tracker.com","https://t-odx.op-mobile.opera.com/impr"],"jstracker":""}}', 'adomain': [ 'opera.com', @@ -782,7 +782,7 @@ describe('Opera Ads Bid Adapter', function () { } const userSyncPixels = spec.getUserSyncs(syncOptions) expect(userSyncPixels).to.have.lengthOf(1); - expect(userSyncPixels[0].url).to.equal('https://s.adx.opera.com/usersync/page') + expect(userSyncPixels[0].url).to.equal('https://s.oa.opera.com/usersync/page') }); }); diff --git a/test/spec/modules/optableRtdProvider_spec.js b/test/spec/modules/optableRtdProvider_spec.js index 7aa4be3c8b2..271d31d0185 100644 --- a/test/spec/modules/optableRtdProvider_spec.js +++ b/test/spec/modules/optableRtdProvider_spec.js @@ -29,15 +29,15 @@ describe('Optable RTD Submodule', function () { expect(parseConfig(config).bundleUrl).to.equal('https://cdn.optable.co/bundle.js'); }); - it('throws an error for invalid bundleUrl format', function () { - expect(() => parseConfig({params: {bundleUrl: 'invalidURL'}})).to.throw(); - expect(() => parseConfig({params: {bundleUrl: 'www.invalid.com'}})).to.throw(); + it('returns null bundleUrl for invalid bundleUrl format', function () { + expect(parseConfig({params: {bundleUrl: 'invalidURL'}}).bundleUrl).to.be.null; + expect(parseConfig({params: {bundleUrl: 'www.invalid.com'}}).bundleUrl).to.be.null; }); - it('throws an error for non-HTTPS bundleUrl', function () { - expect(() => parseConfig({params: {bundleUrl: 'http://cdn.optable.co/bundle.js'}})).to.throw(); - expect(() => parseConfig({params: {bundleUrl: '//cdn.optable.co/bundle.js'}})).to.throw(); - expect(() => parseConfig({params: {bundleUrl: '/bundle.js'}})).to.throw(); + it('returns null bundleUrl for non-HTTPS bundleUrl', function () { + expect(parseConfig({params: {bundleUrl: 'http://cdn.optable.co/bundle.js'}}).bundleUrl).to.be.null; + expect(parseConfig({params: {bundleUrl: '//cdn.optable.co/bundle.js'}}).bundleUrl).to.be.null; + expect(parseConfig({params: {bundleUrl: '/bundle.js'}}).bundleUrl).to.be.null; }); it('defaults adserverTargeting to true if missing', function () { @@ -46,8 +46,8 @@ describe('Optable RTD Submodule', function () { ).adserverTargeting).to.be.true; }); - it('throws an error if handleRtd is not a function', function () { - expect(() => parseConfig({params: {handleRtd: 'notAFunction'}})).to.throw(); + it('returns null handleRtd if handleRtd is not a function', function () { + expect(parseConfig({params: {handleRtd: 'notAFunction'}}).handleRtd).to.be.null; }); }); @@ -81,7 +81,14 @@ describe('Optable RTD Submodule', function () { it('does nothing if targeting data is missing the ortb2 property', async function () { window.optable.instance.targetingFromCache.returns({}); - window.optable.instance.targeting.resolves({}); + + // Dispatch event with empty ortb2 data after a short delay + setTimeout(() => { + const event = new CustomEvent('optable-targeting:change', { + detail: {} + }); + window.dispatchEvent(event); + }, 10); await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); expect(mergeFn.called).to.be.false; @@ -98,7 +105,14 @@ describe('Optable RTD Submodule', function () { it('calls targeting function if no data is found in cache', async function () { const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}}; window.optable.instance.targetingFromCache.returns(null); - window.optable.instance.targeting.resolves(targetingData); + + // Dispatch event with targeting data after a short delay + setTimeout(() => { + const event = new CustomEvent('optable-targeting:change', { + detail: targetingData + }); + window.dispatchEvent(event); + }, 10); await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true; @@ -162,18 +176,33 @@ describe('Optable RTD Submodule', function () { it('calls callback when assuming the bundle is present', function (done) { moduleConfig.params.bundleUrl = null; + window.optable = { + cmd: [], + instance: { + targetingFromCache: sandbox.stub().returns(null) + } + }; getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); // Check that the function is queued expect(window.optable.cmd.length).to.equal(1); + + // Dispatch the event after a short delay + setTimeout(() => { + const event = new CustomEvent('optable-targeting:change', { + detail: {ortb2: {user: {ext: {optable: 'testData'}}}} + }); + window.dispatchEvent(event); + }, 10); + // Manually trigger the queued function window.optable.cmd[0](); setTimeout(() => { expect(callback.calledOnce).to.be.true; done(); - }, 50); + }, 100); }); it('mergeOptableData catches error and executes callback when something goes wrong', function (done) { @@ -194,26 +223,64 @@ describe('Optable RTD Submodule', function () { it('getBidRequestData catches error and executes callback when something goes wrong', function (done) { moduleConfig.params.bundleUrl = null; moduleConfig.params.handleRtd = 'not a function'; + window.optable = { + cmd: [], + instance: { + targetingFromCache: sandbox.stub().returns(null) + } + }; getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); - expect(window.optable.cmd.length).to.equal(0); + expect(window.optable.cmd.length).to.equal(1); + + // Dispatch event after a short delay + setTimeout(() => { + const event = new CustomEvent('optable-targeting:change', { + detail: {ortb2: {user: {ext: {optable: 'testData'}}}} + }); + window.dispatchEvent(event); + }, 10); + + // Execute the queued command + window.optable.cmd[0](); setTimeout(() => { expect(callback.calledOnce).to.be.true; done(); - }, 50); + }, 100); }); it("doesn't fail when optable is not available", function (done) { + moduleConfig.params.bundleUrl = null; delete window.optable; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); - expect(window?.optable?.cmd?.length).to.be.undefined; + + // The code should have created window.optable with cmd array + expect(window.optable).to.exist; + expect(window.optable.cmd.length).to.equal(1); + + // Simulate optable bundle initializing and executing commands + window.optable.instance = { + targetingFromCache: () => null + }; + + // Dispatch event after a short delay + setTimeout(() => { + const event = new CustomEvent('optable-targeting:change', { + detail: {ortb2: {user: {ext: {optable: 'testData'}}}} + }); + window.dispatchEvent(event); + }, 10); + + // Execute the queued command (simulating optable bundle execution) + window.optable.cmd[0](); setTimeout(() => { expect(callback.calledOnce).to.be.true; done(); - }, 50); + }, 100); }); }); diff --git a/test/spec/modules/optidigitalBidAdapter_spec.js b/test/spec/modules/optidigitalBidAdapter_spec.js index 3b4ef61e961..bb0526d9aaa 100755 --- a/test/spec/modules/optidigitalBidAdapter_spec.js +++ b/test/spec/modules/optidigitalBidAdapter_spec.js @@ -63,8 +63,7 @@ describe('optidigitalAdapterTests', function () { 'adserver': { 'name': 'gam', 'adslot': '/19968336/header-bid-tag-0' - }, - 'pbadslot': '/19968336/header-bid-tag-0' + } }, 'gpid': '/19968336/header-bid-tag-0' } @@ -212,7 +211,8 @@ describe('optidigitalAdapterTests', function () { it('should send bid request to given endpoint', function() { const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.url).to.equal(ENDPOINT); + const finalEndpoint = `${ENDPOINT}/s123`; + expect(request.url).to.equal(finalEndpoint); }); it('should be bidRequest data', function () { @@ -282,6 +282,49 @@ describe('optidigitalAdapterTests', function () { expect(payload.imp[0].adContainerHeight).to.exist; }); + it('should read container size from DOM when divId exists', function () { + const containerId = 'od-test-container'; + const el = document.createElement('div'); + el.id = containerId; + el.style.width = '321px'; + el.style.height = '111px'; + el.style.position = 'absolute'; + el.style.left = '0'; + el.style.top = '0'; + document.body.appendChild(el); + + const validBidRequestsWithDivId = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'divId': containerId + }, + 'adUnitCode': containerId, + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ]; + + const request = spec.buildRequests(validBidRequestsWithDivId, bidderRequest); + const payload = JSON.parse(request.data); + try { + expect(payload.imp[0].adContainerWidth).to.equal(el.offsetWidth); + expect(payload.imp[0].adContainerHeight).to.equal(el.offsetHeight); + } finally { + document.body.removeChild(el); + } + }); + it('should add pageTemplate to payload if pageTemplate exsists in parameter', function () { const validBidRequestsWithDivId = [ { @@ -325,7 +368,8 @@ describe('optidigitalAdapterTests', function () { 'domain': 'example.com', 'publisher': { 'domain': 'example.com' - } + }, + 'keywords': 'key1,key2' }, 'device': { 'w': 1507, @@ -386,6 +430,12 @@ describe('optidigitalAdapterTests', function () { expect(payload.bapp).to.deep.equal(validBidRequests[0].params.bapp); }); + it('should add keywords to payload when site keywords present', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.site.keywords).to.deep.equal('key1,key2'); + }); + it('should send empty GDPR consent and required set to false', function() { const request = spec.buildRequests(validBidRequests, bidderRequest); const payload = JSON.parse(request.data); @@ -401,6 +451,7 @@ describe('optidigitalAdapterTests', function () { 'vendorData': { 'hasGlobalConsent': false }, + 'addtlConsent': '1~7.12.35.62.66.70.89.93.108', 'apiVersion': 1 } const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -408,6 +459,7 @@ describe('optidigitalAdapterTests', function () { expect(payload.gdpr).to.exist; expect(payload.gdpr.consent).to.equal(consentString); expect(payload.gdpr.required).to.exist.and.to.be.true; + expect(payload.gdpr.addtlConsent).to.exist; }); it('should send empty GDPR consent to endpoint', function() { @@ -425,6 +477,22 @@ describe('optidigitalAdapterTests', function () { expect(payload.gdpr.consent).to.equal(''); }); + it('should send GDPR consent and required set to false when gdprApplies is not boolean', function() { + let consentString = 'DFR8KRePoQNsRREZCADBG+A=='; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': "", + 'vendorData': { + 'hasGlobalConsent': false + }, + 'apiVersion': 1 + } + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr.consent).to.equal(consentString); + expect(payload.gdpr.required).to.exist.and.to.be.false; + }); + it('should send uspConsent to given endpoint', function() { bidderRequest.uspConsent = '1YYY'; const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -457,6 +525,21 @@ describe('optidigitalAdapterTests', function () { expect(payload.gpp).to.exist; }); + it('should set testMode when optidigitalTestMode flag present in location', function() { + const originalUrl = window.location.href; + const newUrl = originalUrl.includes('optidigitalTestMode=true') + ? originalUrl + : `${window.location.pathname}${window.location.search}${window.location.search && window.location.search.length ? '&' : '?'}optidigitalTestMode=true${window.location.hash || ''}`; + try { + window.history.pushState({}, '', newUrl); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.testMode).to.equal(true); + } finally { + window.history.replaceState({}, '', originalUrl); + } + }); + it('should use appropriate mediaTypes banner sizes', function() { const mediaTypesBannerSize = { 'mediaTypes': { @@ -532,6 +615,23 @@ describe('optidigitalAdapterTests', function () { expect(payload.user).to.deep.equal(undefined); }); + it('should add gpid to payload when gpid', function() { + validBidRequests[0].ortb2Imp = { + 'ext': { + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/19968336/header-bid-tag-0' + } + }, + 'gpid': '/19968336/header-bid-tag-0' + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].gpid).to.deep.equal('/19968336/header-bid-tag-0'); + }); + function returnBannerSizes(mediaTypes, expectedSizes) { const bidRequest = Object.assign(validBidRequests[0], mediaTypes); const request = spec.buildRequests([bidRequest], bidderRequest); diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js index 4a6b8fa7d36..f3f31be3e30 100644 --- a/test/spec/modules/orakiBidAdapter_spec.js +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -478,7 +478,7 @@ describe('OrakiBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -487,9 +487,7 @@ describe('OrakiBidAdapter', function () { 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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -498,7 +496,7 @@ describe('OrakiBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/panxoBidAdapter_spec.js b/test/spec/modules/panxoBidAdapter_spec.js new file mode 100644 index 00000000000..4ce770aec12 --- /dev/null +++ b/test/spec/modules/panxoBidAdapter_spec.js @@ -0,0 +1,346 @@ +import { expect } from 'chai'; +import { spec, storage } from 'modules/panxoBidAdapter.js'; +import { BANNER } from 'src/mediaTypes.js'; + +describe('PanxoBidAdapter', function () { + const PROPERTY_KEY = 'abc123def456'; + const USER_ID = 'test-user-id-12345'; + + // Mock storage.getDataFromLocalStorage + let getDataStub; + + beforeEach(function () { + getDataStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataStub.withArgs('panxo_uid').returns(USER_ID); + }); + + afterEach(function () { + getDataStub.restore(); + }); + + describe('isBidRequestValid', function () { + it('should return true when propertyKey is present', function () { + const bid = { + bidder: 'panxo', + params: { propertyKey: PROPERTY_KEY }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when propertyKey is missing', function () { + const bid = { + bidder: 'panxo', + params: {}, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when banner mediaType is missing', function () { + const bid = { + bidder: 'panxo', + params: { propertyKey: PROPERTY_KEY }, + mediaTypes: { video: {} } + }; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + const bidderRequest = { + bidderRequestId: 'test-request-id', + auctionId: 'test-auction-id', + timeout: 1500, + refererInfo: { + page: 'https://example.com/page', + domain: 'example.com', + ref: 'https://google.com' + } + }; + + const validBidRequests = [{ + bidder: 'panxo', + bidId: 'bid-id-1', + adUnitCode: 'ad-unit-1', + params: { propertyKey: PROPERTY_KEY }, + mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] } } + }]; + + it('should build a valid OpenRTB request', function () { + const requests = spec.buildRequests(validBidRequests, bidderRequest); + + expect(requests).to.be.an('array').with.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.include('panxo-sys.com/openrtb/2.5/bid'); + expect(requests[0].url).to.include(`key=${PROPERTY_KEY}`); + expect(requests[0].data).to.be.an('object'); + }); + + it('should include user.buyeruid from localStorage', function () { + const requests = spec.buildRequests(validBidRequests, bidderRequest); + + expect(requests[0].data.user).to.be.an('object'); + expect(requests[0].data.user.buyeruid).to.equal(USER_ID); + }); + + it('should build correct impressions', function () { + const requests = spec.buildRequests(validBidRequests, bidderRequest); + + expect(requests[0].data.imp).to.be.an('array'); + expect(requests[0].data.imp[0].id).to.equal('bid-id-1'); + expect(requests[0].data.imp[0].banner.format).to.have.lengthOf(2); + expect(requests[0].data.imp[0].tagid).to.equal('ad-unit-1'); + }); + + it('should return empty array when panxo_uid is not found', function () { + getDataStub.withArgs('panxo_uid').returns(null); + const requests = spec.buildRequests(validBidRequests, bidderRequest); + + expect(requests).to.be.an('array').that.is.empty; + }); + + it('should include GDPR consent when gdprApplies is true', function () { + const gdprBidderRequest = { + ...bidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'CO-test-consent-string' + } + }; + const requests = spec.buildRequests(validBidRequests, gdprBidderRequest); + + expect(requests[0].data.regs.ext.gdpr).to.equal(1); + expect(requests[0].data.user.ext.consent).to.equal('CO-test-consent-string'); + }); + + it('should not include gdpr flag when gdprApplies is undefined', function () { + const gdprBidderRequest = { + ...bidderRequest, + gdprConsent: { + gdprApplies: undefined, + consentString: 'CO-test-consent-string' + } + }; + const requests = spec.buildRequests(validBidRequests, gdprBidderRequest); + + expect(requests[0].data.regs.ext.gdpr).to.be.undefined; + expect(requests[0].data.user.ext.consent).to.equal('CO-test-consent-string'); + }); + + it('should include USP consent when available', function () { + const uspBidderRequest = { + ...bidderRequest, + uspConsent: '1YNN' + }; + const requests = spec.buildRequests(validBidRequests, uspBidderRequest); + + expect(requests[0].data.regs.ext.us_privacy).to.equal('1YNN'); + }); + + it('should include schain when available', function () { + const schainBidderRequest = { + ...bidderRequest, + ortb2: { + source: { + ext: { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'example.com', sid: '12345', hp: 1 }] + } + } + } + } + }; + const requests = spec.buildRequests(validBidRequests, schainBidderRequest); + + expect(requests[0].data.source.ext.schain).to.deep.equal(schainBidderRequest.ortb2.source.ext.schain); + }); + + it('should use floor from getFloor function', function () { + const bidWithFloor = [{ + ...validBidRequests[0], + getFloor: () => ({ currency: 'USD', floor: 1.50 }) + }]; + const requests = spec.buildRequests(bidWithFloor, bidderRequest); + + expect(requests[0].data.imp[0].bidfloor).to.equal(1.50); + }); + + it('should include full ortb2Imp object in impression', function () { + const bidWithOrtb2Imp = [{ + ...validBidRequests[0], + ortb2Imp: { + instl: 1, + ext: { data: { customField: 'value' } } + } + }]; + const requests = spec.buildRequests(bidWithOrtb2Imp, bidderRequest); + + expect(requests[0].data.imp[0].instl).to.equal(1); + expect(requests[0].data.imp[0].ext.data.customField).to.equal('value'); + }); + + it('should split requests by different propertyKeys', function () { + const multiPropertyBids = [ + { + bidder: 'panxo', + bidId: 'bid-id-1', + adUnitCode: 'ad-unit-1', + params: { propertyKey: 'property-a' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }, + { + bidder: 'panxo', + bidId: 'bid-id-2', + adUnitCode: 'ad-unit-2', + params: { propertyKey: 'property-b' }, + mediaTypes: { banner: { sizes: [[728, 90]] } } + } + ]; + const requests = spec.buildRequests(multiPropertyBids, bidderRequest); + + expect(requests).to.have.lengthOf(2); + expect(requests[0].url).to.include('key=property-a'); + expect(requests[1].url).to.include('key=property-b'); + }); + }); + + describe('interpretResponse', function () { + const request = { + bidderRequest: { + bids: [{ bidId: 'bid-id-1', adUnitCode: 'ad-unit-1' }] + } + }; + + const serverResponse = { + body: { + id: 'response-id', + seatbid: [{ + seat: 'panxo', + bid: [{ + impid: 'bid-id-1', + price: 2.50, + w: 300, + h: 250, + adm: '
Ad creative
', + crid: 'creative-123', + adomain: ['advertiser.com'], + nurl: 'https://panxo-sys.com/win?price=${AUCTION_PRICE}' + }] + }], + cur: 'USD' + } + }; + + it('should parse valid bid response', function () { + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('bid-id-1'); + expect(bids[0].cpm).to.equal(2.50); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.be.true; + expect(bids[0].ad).to.equal('
Ad creative
'); + expect(bids[0].meta.advertiserDomains).to.include('advertiser.com'); + }); + + it('should return empty array for empty response', function () { + const emptyResponse = { body: {} }; + const bids = spec.interpretResponse(emptyResponse, request); + + expect(bids).to.be.an('array').that.is.empty; + }); + + it('should return empty array for no seatbid', function () { + const noSeatbidResponse = { body: { id: 'test', seatbid: [] } }; + const bids = spec.interpretResponse(noSeatbidResponse, request); + + expect(bids).to.be.an('array').that.is.empty; + }); + + it('should include nurl in bid response', function () { + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids[0].nurl).to.include('panxo-sys.com/win'); + }); + }); + + describe('getUserSyncs', function () { + it('should return pixel sync when enabled', function () { + const syncOptions = { pixelEnabled: true }; + const syncs = spec.getUserSyncs(syncOptions); + + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.include('panxo-sys.com/usersync'); + }); + + it('should return empty array when pixel sync disabled', function () { + const syncOptions = { pixelEnabled: false }; + const syncs = spec.getUserSyncs(syncOptions); + + expect(syncs).to.be.an('array').that.is.empty; + }); + + it('should include GDPR params when gdprApplies is true', function () { + const syncOptions = { pixelEnabled: true }; + const gdprConsent = { gdprApplies: true, consentString: 'test-consent' }; + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=test-consent'); + }); + + it('should not include gdpr flag when gdprApplies is undefined', function () { + const syncOptions = { pixelEnabled: true }; + const gdprConsent = { gdprApplies: undefined, consentString: 'test-consent' }; + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + + expect(syncs[0].url).to.not.include('gdpr='); + expect(syncs[0].url).to.include('gdpr_consent=test-consent'); + }); + + it('should include USP params when available', function () { + const syncOptions = { pixelEnabled: true }; + const uspConsent = '1YNN'; + const syncs = spec.getUserSyncs(syncOptions, [], null, uspConsent); + + expect(syncs[0].url).to.include('us_privacy=1YNN'); + }); + }); + + describe('onBidWon', function () { + it('should fire win notification pixel', function () { + const bid = { + nurl: 'https://panxo-sys.com/win?price=${AUCTION_PRICE}', + cpm: 2.50 + }; + + // Mock document.createElement + const imgStub = { src: '', style: {} }; + const createElementStub = sinon.stub(document, 'createElement').returns(imgStub); + const appendChildStub = sinon.stub(document.body, 'appendChild'); + + spec.onBidWon(bid); + + expect(imgStub.src).to.include('price=2.5'); + + createElementStub.restore(); + appendChildStub.restore(); + }); + }); + + describe('spec properties', function () { + it('should have correct bidder code', function () { + expect(spec.code).to.equal('panxo'); + }); + + it('should support banner media type', function () { + expect(spec.supportedMediaTypes).to.include(BANNER); + }); + }); +}); diff --git a/test/spec/modules/panxoRtdProvider_spec.js b/test/spec/modules/panxoRtdProvider_spec.js new file mode 100644 index 00000000000..d3e0cbe12bf --- /dev/null +++ b/test/spec/modules/panxoRtdProvider_spec.js @@ -0,0 +1,311 @@ +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/panxoRtdProvider.js'; + +const { + SUBMODULE_NAME, + SCRIPT_URL, + main, + load, + onImplLoaded, + onImplMessage, + onGetBidRequestData, + flushPendingCallbacks +} = __TEST__; + +describe('panxo RTD module', function () { + let sandbox; + + const stubUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + const panxoBridgeId = `panxo_${stubUuid}`; + const stubWindow = { [panxoBridgeId]: undefined }; + + const validSiteId = 'a1b2c3d4e5f67890'; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + 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.createSandbox(); + connectSpy = sandbox.spy(); + // Simulate: once the impl script is loaded, it registers the bridge API + sandbox2.stub(stubWindow, panxoBridgeId).value({ connect: connectSpy }); + }); + afterEach(function () { + sandbox2.restore(); + }); + + it('should accept valid configuration with siteId', function () { + expect(() => load({ params: { siteId: validSiteId } })).to.not.throw(); + }); + + it('should throw an Error when siteId is missing', function () { + expect(() => load({})).to.throw(); + expect(() => load({ params: {} })).to.throw(); + expect(() => load({ params: { siteId: '' } })).to.throw(); + }); + + it('should throw an Error when siteId is not a valid hex string', function () { + expect(() => load({ params: { siteId: 'abc' } })).to.throw(); + expect(() => load({ params: { siteId: 123 } })).to.throw(); + expect(() => load({ params: { siteId: 'zzzzzzzzzzzzzzzz' } })).to.throw(); + expect(() => load({ params: { siteId: 'a1b2c3d4e5f6789' } })).to.throw(); // 15 chars + expect(() => load({ params: { siteId: 'a1b2c3d4e5f678901' } })).to.throw(); // 17 chars + }); + + it('should insert implementation script with correct URL', () => { + load({ params: { siteId: validSiteId } }); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + + const args = loadExternalScriptStub.getCall(0).args; + expect(args[0]).to.be.equal( + `${SCRIPT_URL}?siteId=${validSiteId}&session=${stubUuid}&r=example.com` + ); + expect(args[2]).to.be.equal(SUBMODULE_NAME); + expect(args[3]).to.be.equal(onImplLoaded); + }); + + it('should connect to the implementation script once it loads', function () { + load({ params: { siteId: validSiteId } }); + + 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); + }); + + it('should flush pending callbacks when bridge is unavailable', function () { + sandbox2.restore(); + // Bridge is not registered on the window -- onImplLoaded should fail open + sandbox2 = sinon.createSandbox(); + sandbox2.stub(stubWindow, panxoBridgeId).value(undefined); + + load({ params: { siteId: validSiteId } }); + + const callbackSpy = sandbox2.spy(); + const reqBidsConfig = { ortb2Fragments: { bidder: {}, global: {} } }; + + // Queue a callback before bridge fails + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + // onImplLoaded already ran (bridge undefined) and flushed + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('should not throw when bridge message is null', function () { + load({ params: { siteId: validSiteId } }); + expect(() => onImplMessage(null)).to.not.throw(); + expect(() => onImplMessage(undefined)).to.not.throw(); + }); + }); + + describe('Bid enrichment step', function () { + const signalData = { + device: { v1: 'session-token-123' }, + site: { ai: true, src: 'chatgpt', conf: 0.95, seg: 'technology', co: 'US' } + }; + + let sandbox2; + let callbackSpy; + let reqBidsConfig; + beforeEach(function() { + sandbox2 = sinon.createSandbox(); + callbackSpy = sandbox2.spy(); + reqBidsConfig = { ortb2Fragments: { bidder: {}, global: {} } }; + // Prevent onImplLoaded from firing automatically so tests can + // control module readiness via onImplMessage directly. + loadExternalScriptStub.callsFake(() => {}); + }); + afterEach(function () { + loadExternalScriptStub.reset(); + sandbox2.restore(); + }); + + it('should defer callback when implementation has not sent a signal yet', () => { + load({ params: { siteId: validSiteId } }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + // Callback is deferred until the implementation script sends its first signal + expect(callbackSpy.notCalled).to.be.true; + }); + + it('should flush deferred callback once a signal arrives', () => { + load({ params: { siteId: validSiteId } }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + expect(callbackSpy.notCalled).to.be.true; + + // First signal arrives -- deferred callback should now fire + onImplMessage({ type: 'signal', data: { device: { v1: 'tok' }, site: {} } }); + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('should flush all deferred callbacks when the first signal arrives', () => { + load({ params: { siteId: validSiteId } }); + + const spy1 = sandbox2.spy(); + const spy2 = sandbox2.spy(); + const cfg1 = { ortb2Fragments: { bidder: {}, global: {} } }; + const cfg2 = { ortb2Fragments: { bidder: {}, global: {} } }; + + onGetBidRequestData(cfg1, spy1, { params: {} }, {}); + onGetBidRequestData(cfg2, spy2, { params: {} }, {}); + expect(spy1.notCalled).to.be.true; + expect(spy2.notCalled).to.be.true; + + onImplMessage({ type: 'signal', data: { device: { v1: 'tok' }, site: {} } }); + expect(spy1.calledOnce).to.be.true; + expect(spy2.calledOnce).to.be.true; + }); + + it('should call callback immediately once implementation is ready', () => { + load({ params: { siteId: validSiteId } }); + + // Mark implementation as ready via a signal + onImplMessage({ type: 'signal', data: { device: { v1: 'tok' }, site: {} } }); + + // Subsequent calls should resolve immediately + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('should call callback when implementation reports an error', () => { + load({ params: { siteId: validSiteId } }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + expect(callbackSpy.notCalled).to.be.true; + + // An error still unblocks the auction + onImplMessage({ type: 'error', data: 'some error' }); + expect(callbackSpy.calledOnce).to.be.true; + // No device or site should be added since panxoData is empty + }); + + it('should add device.ext.panxo with session token when signal is received', () => { + load({ params: { siteId: validSiteId } }); + + onImplMessage({ type: 'signal', data: signalData }); + 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('panxo') + .which.is.an('object') + .that.deep.equals(signalData.device); + }); + + it('should add site.ext.data.panxo with AI classification data', () => { + load({ params: { siteId: validSiteId } }); + + onImplMessage({ type: 'signal', data: signalData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('site'); + expect(reqBidsConfig.ortb2Fragments.global.site).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.site.ext).to.have.own.property('data'); + expect(reqBidsConfig.ortb2Fragments.global.site.ext.data).to.have.own.property('panxo') + .which.is.an('object') + .that.deep.equals(signalData.site); + }); + + it('should update panxo data when new signal is received', () => { + load({ params: { siteId: validSiteId } }); + + const updatedData = { + device: { v1: 'updated-token' }, + site: { ai: true, src: 'perplexity', conf: 0.88, seg: 'finance', co: 'UK' } + }; + + onImplMessage({ type: 'signal', data: { device: { v1: 'old-token' }, site: {} } }); + onImplMessage({ type: 'signal', data: updatedData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global.device.ext.panxo) + .to.deep.equal(updatedData.device); + expect(reqBidsConfig.ortb2Fragments.global.site.ext.data.panxo) + .to.deep.equal(updatedData.site); + }); + + it('should not add site data when site object is empty', () => { + load({ params: { siteId: validSiteId } }); + + onImplMessage({ type: 'signal', data: { device: { v1: 'token' }, site: {} } }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global.device.ext.panxo) + .to.deep.equal({ v1: 'token' }); + // site should not have panxo data since it was empty + expect(reqBidsConfig.ortb2Fragments.global).to.not.have.own.property('site'); + }); + }); + + describe('Submodule execution', function() { + let sandbox2; + let submoduleStub; + beforeEach(function() { + sandbox2 = sinon.createSandbox(); + 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 panxo RTD submodule provider', function () { + getModule(); + }); + + it('should refuse initialization when siteId is missing', function () { + const { init } = getModule(); + expect(init({ params: {} })).to.equal(false); + expect(loadExternalScriptStub.notCalled).to.be.true; + }); + + it('should refuse initialization when siteId is invalid', function () { + const { init } = getModule(); + expect(init({ params: { siteId: 'invalid' } })).to.equal(false); + expect(loadExternalScriptStub.notCalled).to.be.true; + }); + + it('should commence initialization with valid siteId', function () { + const { init } = getModule(); + expect(init({ params: { siteId: validSiteId } })).to.equal(true); + expect(loadExternalScriptStub.calledOnce).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/permutiveCombined_spec.js b/test/spec/modules/permutiveCombined_spec.js index dbf82d68fee..244558d8378 100644 --- a/test/spec/modules/permutiveCombined_spec.js +++ b/test/spec/modules/permutiveCombined_spec.js @@ -14,7 +14,7 @@ import { } from 'modules/permutiveRtdProvider.js' import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' -import { permutiveIdentityManagerIdSubmodule } from '../../../modules/permutiveIdentityManagerIdSystem.js' +import { permutiveIdentityManagerIdSubmodule, storage as permutiveIdStorage } from '../../../modules/permutiveIdentityManagerIdSystem.js' describe('permutiveRtdProvider', function () { beforeEach(function () { @@ -35,6 +35,84 @@ describe('permutiveRtdProvider', function () { }) }) + describe('consent handling', function () { + const publisherPurposeConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + publisher: { consents: { 1: true }, legitimateInterests: {} }, + vendor: { consents: {}, legitimateInterests: {} }, + purpose: { consents: {}, legitimateInterests: {} }, + } + } + } + + const vendorPurposeConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + publisher: { consents: {}, legitimateInterests: {} }, + vendor: { consents: { 361: true }, legitimateInterests: {} }, + purpose: { consents: { 1: true }, legitimateInterests: {} }, + } + } + } + + const missingVendorConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + publisher: { consents: { 1: true }, legitimateInterests: {} }, + vendor: { consents: {}, legitimateInterests: {} }, + purpose: { consents: { 1: true }, legitimateInterests: {} }, + } + } + } + + it('allows publisher consent path when vendor check is disabled', function () { + expect(permutiveSubmodule.init({}, publisherPurposeConsent)).to.equal(true) + }) + + it('requires vendor consent when enforceVendorConsent is enabled', function () { + expect(permutiveSubmodule.init({ params: { enforceVendorConsent: true } }, missingVendorConsent)).to.equal(false) + }) + + it('allows vendor consent path when enforceVendorConsent is enabled', function () { + expect(permutiveSubmodule.init({ params: { enforceVendorConsent: true } }, vendorPurposeConsent)).to.equal(true) + }) + + describe('identity manager gating', function () { + const idKey = 'permutive-prebid-id' + const idPayload = { providers: { id5id: { userId: 'abc', expiryTime: Date.now() + 10000 } } } + + beforeEach(function () { + permutiveIdStorage.setDataInLocalStorage(idKey, JSON.stringify(idPayload)) + }) + + afterEach(function () { + permutiveIdStorage.removeDataFromLocalStorage(idKey) + }) + + it('returns ids with publisher consent when vendor enforcement is disabled', function () { + const response = permutiveIdentityManagerIdSubmodule.getId({}, publisherPurposeConsent) + + expect(response).to.deep.equal({ id: { id5id: 'abc' } }) + }) + + it('blocks ids when vendor consent is missing and enforcement is enabled', function () { + const response = permutiveIdentityManagerIdSubmodule.getId({ params: { enforceVendorConsent: true } }, missingVendorConsent) + + expect(response).to.be.undefined + }) + + it('returns ids when vendor consent is present and enforcement is enabled', function () { + const response = permutiveIdentityManagerIdSubmodule.getId({ params: { enforceVendorConsent: true } }, vendorPurposeConsent) + + expect(response).to.deep.equal({ id: { id5id: 'abc' } }) + }) + }) + }) + describe('getModuleConfig', function () { beforeEach(function () { // Reads data from the cache @@ -49,6 +127,7 @@ describe('permutiveRtdProvider', function () { maxSegs: 500, acBidders: [], overwrites: {}, + enforceVendorConsent: false, }, }) @@ -951,7 +1030,7 @@ describe('permutiveIdentityManagerIdSystem', () => { }) describe('decode', () => { - it('returns the input unchanged', () => { + it('returns the input unchanged for most IDs', () => { const input = { id5id: { uid: '0', @@ -965,6 +1044,17 @@ describe('permutiveIdentityManagerIdSystem', () => { const result = permutiveIdentityManagerIdSubmodule.decode(input) expect(result).to.be.equal(input) }) + + it('decodes the base64-encoded array for pairId', () => { + const input = { + pairId: 'WyJBeVhiNUF0dmsvVS8xQ1d2ejJuRVk5aFl4T1g3TVFPUTJVQk1BMFdiV1ZFbSJd' + } + const result = permutiveIdentityManagerIdSubmodule.decode(input) + const expected = { + pairId: ["AyXb5Atvk/U/1CWvz2nEY9hYxOX7MQOQ2UBMA0WbWVEm"] + } + expect(result).to.deep.equal(expected) + }) }) describe('getId', () => { @@ -988,6 +1078,46 @@ describe('permutiveIdentityManagerIdSystem', () => { expect(result).to.deep.equal(expected) }) + it('handles idl_env without pairId', () => { + const data = { + 'providers': { + 'idl_env': { + 'userId': 'ats_envelope_value' + } + } + } + storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify(data)) + const result = permutiveIdentityManagerIdSubmodule.getId({}) + const expected = { + 'id': { + 'idl_env': 'ats_envelope_value' + } + } + expect(result).to.deep.equal(expected) + }) + + it('handles idl_env with pairId', () => { + const data = { + 'providers': { + 'idl_env': { + 'userId': 'ats_envelope_value', + }, + 'pairId': { + 'userId': 'pair_id_encoded_value' + } + } + } + storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify(data)) + const result = permutiveIdentityManagerIdSubmodule.getId({}) + const expected = { + 'id': { + 'idl_env': 'ats_envelope_value', + 'pairId': 'pair_id_encoded_value' + } + } + expect(result).to.deep.equal(expected) + }) + it('returns undefined if no relevant IDs are found in localStorage', () => { storage.setDataInLocalStorage(STORAGE_KEY, '{}') const result = permutiveIdentityManagerIdSubmodule.getId({}) diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index 6eea9bec92a..b881be50402 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -481,7 +481,7 @@ describe('PGAMBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -490,9 +490,7 @@ describe('PGAMBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pgammedia.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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -501,7 +499,7 @@ describe('PGAMBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pgammedia.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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 882f38ba2a9..595b95c6db8 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -7,7 +7,7 @@ import { } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager, {PBS_ADAPTER_NAME} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {deepAccess, deepClone, mergeDeep} from 'src/utils.js'; +import {deepAccess, deepClone, getWinDimensions, mergeDeep} from 'src/utils.js'; import {ajax} from 'src/ajax.js'; import {config} from 'src/config.js'; import * as events from 'src/events.js'; @@ -1195,8 +1195,8 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.screen.width, - h: window.screen.height, + w: getWinDimensions().screen.width, + h: getWinDimensions().screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1227,8 +1227,8 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.screen.width, - h: window.screen.height, + w: getWinDimensions().screen.width, + h: getWinDimensions().screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1619,8 +1619,8 @@ describe('S2S Adapter', function () { adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { - w: window.screen.width, - h: window.screen.height, + w: getWinDimensions().screen.width, + h: getWinDimensions().screen.height, }) expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index a68da71534b..6795326246f 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -82,6 +82,7 @@ describe('the price floors module', function () { endpoint: {}, enforcement: { enforceJS: true, + enforceBidders: ['*'], enforcePBS: false, floorDeals: false, bidAdjustment: true @@ -95,6 +96,7 @@ describe('the price floors module', function () { endpoint: {}, enforcement: { enforceJS: true, + enforceBidders: ['*'], enforcePBS: false, floorDeals: false, bidAdjustment: true @@ -108,6 +110,7 @@ describe('the price floors module', function () { endpoint: {}, enforcement: { enforceJS: true, + enforceBidders: ['*'], enforcePBS: false, floorDeals: false, bidAdjustment: true @@ -689,6 +692,11 @@ describe('the price floors module', function () { const skipRate = utils.deepAccess(adUnits, '0.bids.0.floorData.skipRate'); expect(skipRate).to.equal(undefined); }); + + it('should not throw when adUnit.bids is undefined', function() { + const adUnitsWithNoBids = [{ code: 'test-ad-unit', mediaTypes: { banner: { sizes: [[300, 250]] } } }]; + expect(() => updateAdUnitsForAuction(adUnitsWithNoBids, inputFloorData, 'id')).to.not.throw(); + }); }); describe('createFloorsDataForAuction', function() { @@ -2281,6 +2289,51 @@ describe('the price floors module', function () { expect(reject.calledOnce).to.be.true; expect(returnedBidResponse).to.not.exist; }); + it('enforces floors for all bidders by default', function () { + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; + returnedBidResponse = null; + runBidResponse({ + ...basicBidResponse, + bidderCode: 'rubicon' + }); + expect(reject.calledOnce).to.be.true; + expect(returnedBidResponse).to.equal(null); + }); + it('enforces floors only for configured enforceBidders when provided', function () { + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].enforcement.enforceBidders = ['rubicon']; + _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; + + runBidResponse({ + ...basicBidResponse, + bidderCode: 'appnexus' + }); + expect(reject.called).to.be.false; + expect(returnedBidResponse).to.haveOwnProperty('floorData'); + + returnedBidResponse = null; + runBidResponse({ + ...basicBidResponse, + bidderCode: 'rubicon' + }); + expect(reject.calledOnce).to.be.true; + expect(returnedBidResponse).to.equal(null); + }); + it('uses adapterCode when checking enforceBidders', function () { + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].enforcement.enforceBidders = ['rubicon']; + _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; + + runBidResponse({ + ...basicBidResponse, + bidderCode: 'alternateBidder', + adapterCode: 'rubicon' + }); + + expect(reject.calledOnce).to.be.true; + expect(returnedBidResponse).to.equal(null); + }); it('if it finds a rule and does not floor should update the bid accordingly', function () { _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 0.3 }; @@ -2294,6 +2347,7 @@ describe('the price floors module', function () { cpmAfterAdjustments: 0.5, enforcements: { bidAdjustment: true, + enforceBidders: ['*'], enforceJS: true, enforcePBS: false, floorDeals: false @@ -2331,6 +2385,7 @@ describe('the price floors module', function () { cpmAfterAdjustments: 0.5, enforcements: { bidAdjustment: true, + enforceBidders: ['*'], enforceJS: true, enforcePBS: false, floorDeals: false @@ -2359,6 +2414,7 @@ describe('the price floors module', function () { cpmAfterAdjustments: 7.5, enforcements: { bidAdjustment: true, + enforceBidders: ['*'], enforceJS: true, enforcePBS: false, floorDeals: false diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 767ef93cf81..a03fb9a8e57 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -1,167 +1,493 @@ -import { expect } from 'chai'; -import { spec } from 'modules/proxistoreBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import { config } from '../../../src/config.js'; +import {expect} from 'chai'; +import {spec} from 'modules/proxistoreBidAdapter.js'; +import {BANNER} from 'src/mediaTypes.js'; const BIDDER_CODE = 'proxistore'; +const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/openrtb'; +const COOKIE_LESS_URL = 'https://abs.cookieless-proxistore.com/v3/rtb/openrtb'; + describe('ProxistoreBidAdapter', function () { const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { + + const baseBidderRequest = { bidderCode: BIDDER_CODE, auctionId: '1025ba77-5463-4877-b0eb-14b205cb9304', bidderRequestId: '10edf38ec1a719', - gdprConsent: { - apiVersion: 2, - gdprApplies: true, - consentString: consentString, - vendorData: { - vendor: { - consents: { - 418: true, - }, + timeout: 1000, + }; + + const gdprConsentWithVendor = { + apiVersion: 2, + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + 418: true, }, }, }, }; - const bid = { - sizes: [[300, 600]], + + const gdprConsentWithoutVendor = { + apiVersion: 2, + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: { + 418: false, + }, + }, + }, + }; + + const gdprConsentNoVendorData = { + apiVersion: 2, + gdprApplies: true, + consentString: consentString, + vendorData: null, + }; + + const baseBid = { + bidder: BIDDER_CODE, params: { website: 'example.fr', language: 'fr', }, - ortb2: { - user: { ext: { data: { segments: [], contextual_categories: {} } } }, + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250]], + }, }, - auctionId: 442133079, - bidId: 464646969, - transactionId: 511916005, + adUnitCode: 'div-gpt-ad-123', + transactionId: '511916005', + bidId: '464646969', + auctionId: '1025ba77-5463-4877-b0eb-14b205cb9304', }; + + describe('spec properties', function () { + it('should have correct bidder code', function () { + expect(spec.code).to.equal(BIDDER_CODE); + }); + + it('should have correct GVLID', function () { + expect(spec.gvlid).to.equal(418); + }); + + it('should support banner media type', function () { + expect(spec.supportedMediaTypes).to.deep.equal([BANNER]); + }); + + it('should have browsingTopics enabled', function () { + expect(spec.browsingTopics).to.be.true; + }); + + it('should have getUserSyncs function', function () { + expect(spec.getUserSyncs).to.be.a('function'); + }); + }); + describe('isBidRequestValid', function () { - it('it should be true if required params are presents and there is no info in the local storage', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + it('should return true when website and language params are present', function () { + expect(spec.isBidRequestValid(baseBid)).to.equal(true); + }); + + it('should return false when website param is missing', function () { + const bid = {...baseBid, params: {language: 'fr'}}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function () { - const date = new Date(); - date.setMinutes(date.getMinutes() - 1); - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); - expect(spec.isBidRequestValid(bid)).to.equal(true); + + it('should return false when language param is missing', function () { + const bid = {...baseBid, params: {website: 'example.fr'}}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function () { - const date = new Date(); - date.setMinutes(date.getMinutes() - 10); - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); - expect(spec.isBidRequestValid(bid)).to.equal(true); + + it('should return false when params object is empty', function () { + const bid = {...baseBid, params: {}}; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); + describe('buildRequests', function () { - const url = { - cookieBase: 'https://abs.proxistore.com/v3/rtb/prebid/multi', - cookieLess: - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi', - }; - - let request = spec.buildRequests([bid], bidderRequest); - it('should return a valid object', function () { - expect(request).to.be.an('object'); - expect(request.method).to.exist; - expect(request.url).to.exist; - expect(request.data).to.exist; - }); - it('request method should be POST', function () { - expect(request.method).to.equal('POST'); - }); - it('should have the value consentGiven to true bc we have 418 in the vendor list', function () { - const data = JSON.parse(request.data); - expect(data.gdpr.consentString).equal( - bidderRequest.gdprConsent.consentString - ); - expect(data.gdpr.applies).to.be.true; - expect(data.gdpr.consentGiven).to.be.true; - }); - it('should contain a valid url', function () { - // has gdpr consent - expect(request.url).equal(url.cookieBase); - // doens't have gdpr consent - bidderRequest.gdprConsent.vendorData = null; - - request = spec.buildRequests([bid], bidderRequest); - expect(request.url).equal(url.cookieLess); - - // api v2 - bidderRequest.gdprConsent = { + describe('request structure', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + + it('should return a valid object', function () { + expect(request).to.be.an('object'); + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + expect(request.options).to.exist; + }); + + it('should use POST method', function () { + expect(request.method).to.equal('POST'); + }); + + it('should have correct options', function () { + expect(request.options.contentType).to.equal('application/json'); + expect(request.options.customHeaders).to.deep.equal({version: '2.0.0'}); + }); + }); + + describe('OpenRTB data format', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + const data = request.data; + + it('should have valid OpenRTB structure', function () { + expect(data).to.be.an('object'); + expect(data.id).to.be.a('string'); + expect(data.imp).to.be.an('array'); + }); + + it('should have imp array with correct length', function () { + expect(data.imp.length).to.equal(1); + }); + + it('should have imp with banner object', function () { + expect(data.imp[0].banner).to.be.an('object'); + expect(data.imp[0].banner.format).to.be.an('array'); + }); + + it('should include banner formats from bid sizes', function () { + const formats = data.imp[0].banner.format; + expect(formats).to.deep.include({w: 300, h: 600}); + expect(formats).to.deep.include({w: 300, h: 250}); + }); + + it('should set imp.id to bidId', function () { + expect(data.imp[0].id).to.equal(baseBid.bidId); + }); + + it('should include tmax from bidderRequest timeout', function () { + expect(data.tmax).to.equal(1000); + }); + + it('should include website and language in ext.proxistore', function () { + expect(data.ext).to.be.an('object'); + expect(data.ext.proxistore).to.be.an('object'); + expect(data.ext.proxistore.website).to.equal('example.fr'); + expect(data.ext.proxistore.language).to.equal('fr'); + }); + }); + + describe('endpoint URL selection', function () { + it('should use cookie URL when GDPR consent is given for vendor 418', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.url).to.equal(COOKIE_BASE_URL); + }); + + it('should use cookieless URL when GDPR applies but consent not given', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithoutVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.url).to.equal(COOKIE_LESS_URL); + }); + + it('should use cookieless URL when vendorData is null', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentNoVendorData}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.url).to.equal(COOKIE_LESS_URL); + }); + + it('should use cookie URL when GDPR does not apply', function () { + const bidderRequest = { + ...baseBidderRequest, + gdprConsent: { + gdprApplies: false, + consentString: consentString, + }, + }; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.url).to.equal(COOKIE_BASE_URL); + }); + + it('should use cookie URL when no gdprConsent object', function () { + const bidderRequest = {...baseBidderRequest}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.url).to.equal(COOKIE_BASE_URL); + }); + }); + + describe('withCredentials option', function () { + it('should set withCredentials to true when consent given', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.options.withCredentials).to.be.true; + }); + + it('should set withCredentials to false when consent not given', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithoutVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.options.withCredentials).to.be.false; + }); + + it('should set withCredentials to false when no vendorData', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentNoVendorData}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.options.withCredentials).to.be.false; + }); + + it('should set withCredentials to false when no gdprConsent', function () { + const bidderRequest = {...baseBidderRequest}; + const request = spec.buildRequests([baseBid], bidderRequest); + expect(request.options.withCredentials).to.be.false; + }); + }); + + describe('multiple bids', function () { + it('should create imp for each bid request', function () { + const secondBid = { + ...baseBid, + bidId: '789789789', + adUnitCode: 'div-gpt-ad-456', + }; + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid, secondBid], bidderRequest); + const data = request.data; + + expect(data.imp.length).to.equal(2); + expect(data.imp[0].id).to.equal(baseBid.bidId); + expect(data.imp[1].id).to.equal(secondBid.bidId); + }); + }); + }); + + describe('interpretResponse', function () { + it('should return empty array for empty response', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + const emptyResponse = {body: null}; + + const bids = spec.interpretResponse(emptyResponse, request); + expect(bids).to.be.an('array'); + expect(bids.length).to.equal(0); + }); + + it('should return empty array for response with no seatbid', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + const response = {body: {id: '123', seatbid: []}}; + + const bids = spec.interpretResponse(response, request); + expect(bids).to.be.an('array'); + expect(bids.length).to.equal(0); + }); + + it('should correctly parse OpenRTB bid response', function () { + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid], bidderRequest); + const requestData = request.data; + + const serverResponse = { + body: { + id: requestData.id, + seatbid: [{ + seat: 'proxistore', + bid: [{ + id: 'bid-id-1', + impid: baseBid.bidId, + price: 6.25, + adm: '
Ad markup
', + w: 300, + h: 600, + crid: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', + dealid: '2021-03_deal123', + adomain: ['advertiser.com'], + }], + }], + cur: 'EUR', + }, + }; + + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.an('array'); + expect(bids.length).to.equal(1); + + const bid = bids[0]; + expect(bid.requestId).to.equal(baseBid.bidId); + expect(bid.cpm).to.equal(6.25); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(600); + expect(bid.ad).to.equal('
Ad markup
'); + expect(bid.creativeId).to.equal('22c3290b-8cd5-4cd6-8e8c-28a2de180ccd'); + expect(bid.dealId).to.equal('2021-03_deal123'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.be.true; + expect(bid.ttl).to.equal(30); + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + }); + + it('should handle multiple bids in response', function () { + const secondBid = { + ...baseBid, + bidId: '789789789', + adUnitCode: 'div-gpt-ad-456', + }; + const bidderRequest = {...baseBidderRequest, gdprConsent: gdprConsentWithVendor}; + const request = spec.buildRequests([baseBid, secondBid], bidderRequest); + const requestData = request.data; + + const serverResponse = { + body: { + id: requestData.id, + seatbid: [{ + seat: 'proxistore', + bid: [ + { + id: 'bid-id-1', + impid: baseBid.bidId, + price: 6.25, + adm: '
Ad 1
', + w: 300, + h: 600, + crid: 'creative-1', + }, + { + id: 'bid-id-2', + impid: secondBid.bidId, + price: 4.50, + adm: '
Ad 2
', + w: 300, + h: 250, + crid: 'creative-2', + }, + ], + }], + cur: 'EUR', + }, + }; + + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.an('array'); + expect(bids.length).to.equal(2); + expect(bids[0].requestId).to.equal(baseBid.bidId); + expect(bids[0].cpm).to.equal(6.25); + expect(bids[1].requestId).to.equal(secondBid.bidId); + expect(bids[1].cpm).to.equal(4.50); + }); + }); + + describe('getUserSyncs', function () { + const SYNC_BASE_URL = 'https://abs.proxistore.com/v3/rtb/sync'; + + it('should return empty array when GDPR applies and consent not given', function () { + const syncOptions = {pixelEnabled: true, iframeEnabled: true}; + const gdprConsent = { gdprApplies: true, - allowAuctionWithoutConsent: true, consentString: consentString, vendorData: { vendor: { - consents: { - 418: true, - }, + consents: {418: false}, }, }, - apiVersion: 2, }; - // has gdpr consent - request = spec.buildRequests([bid], bidderRequest); - expect(request.url).equal(url.cookieBase); - - bidderRequest.gdprConsent.vendorData.vendor = {}; - request = spec.buildRequests([bid], bidderRequest); - expect(request.url).equal(url.cookieLess); - }); - it('should have a property a length of bids equal to one if there is only one bid', function () { - const data = JSON.parse(request.data); - expect(data.hasOwnProperty('bids')).to.be.true; - expect(data.bids).to.be.an('array'); - expect(data.bids.length).equal(1); - expect(data.bids[0].hasOwnProperty('id')).to.be.true; - expect(data.bids[0].sizes).to.be.an('array'); - }); - it('should correctly set bidfloor on imp when getfloor in scope', function () { - let data = JSON.parse(request.data); - expect(data.bids[0].floor).to.be.null; - - bid.params['bidFloor'] = 1; - let req = spec.buildRequests([bid], bidderRequest); - data = JSON.parse(req.data); - expect(data.bids[0].floor).equal(1); - bid.getFloor = function () { - return { currency: 'USD', floor: 1.0 }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(0); + }); + + it('should return pixel sync when pixelEnabled and consent given', function () { + const syncOptions = {pixelEnabled: true, iframeEnabled: false}; + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: {418: true}, + }, + }, }; - req = spec.buildRequests([bid], bidderRequest); - data = JSON.parse(req.data); - expect(data.bids[0].floor).to.be.null; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.include(`${SYNC_BASE_URL}/image`); + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include(`gdpr_consent=${encodeURIComponent(consentString)}`); }); - }); - describe('interpretResponse', function () { - const emptyResponseParam = { body: [] }; - const fakeResponseParam = { - body: [ - { - ad: '', - cpm: 6.25, - creativeId: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', - currency: 'EUR', - dealId: '2021-03_a63ec55e-b9bb-4ca4-b2c9-f456be67e656', - height: 600, - netRevenue: true, - requestId: '3543724f2a033c9', - segments: [], - ttl: 10, - vastUrl: null, - vastXml: null, - width: 300, + + it('should return iframe sync when iframeEnabled and consent given', function () { + const syncOptions = {pixelEnabled: false, iframeEnabled: true}; + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: {418: true}, + }, }, - ], - }; - - it('should always return an array', function () { - let response = spec.interpretResponse(emptyResponseParam, bid); - expect(response).to.be.an('array'); - expect(response.length).equal(0); - response = spec.interpretResponse(fakeResponseParam, bid); - expect(response).to.be.an('array'); - expect(response.length).equal(1); + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(`${SYNC_BASE_URL}/iframe`); + }); + + it('should return both syncs when both enabled and consent given', function () { + const syncOptions = {pixelEnabled: true, iframeEnabled: true}; + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: {418: true}, + }, + }, + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(2); + expect(syncs[0].type).to.equal('image'); + expect(syncs[1].type).to.equal('iframe'); + }); + + it('should return syncs when GDPR does not apply', function () { + const syncOptions = {pixelEnabled: true, iframeEnabled: true}; + const gdprConsent = { + gdprApplies: false, + consentString: consentString, + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(2); + expect(syncs[0].url).to.include('gdpr=0'); + }); + + it('should return syncs when no gdprConsent provided', function () { + const syncOptions = {pixelEnabled: true, iframeEnabled: true}; + + const syncs = spec.getUserSyncs(syncOptions, [], undefined); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(2); + }); + + it('should return empty array when no sync options enabled', function () { + const syncOptions = {pixelEnabled: false, iframeEnabled: false}; + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + vendor: { + consents: {418: true}, + }, + }, + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(0); }); }); }); diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index 97953192a6e..ebf53063c52 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -432,7 +432,7 @@ describe('PubCircleBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -441,9 +441,7 @@ describe('PubCircleBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pubcircle.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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -452,7 +450,7 @@ describe('PubCircleBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index b890a9d575d..c0c3367881d 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -686,6 +686,47 @@ describe('pubmatic analytics adapter', function () { expect(trackerData.rd.ctr).to.equal('US'); }); + it('Logger: does not include identity partners when getUserIds is not a function', function () { + this.timeout(5000); + + // Make sure getUserIds is NOT a function so that getListOfIdentityPartners returns early + const namespace = getGlobal(); + const originalGetUserIds = namespace.getUserIds; + namespace.getUserIds = null; + + sandbox.stub(getGlobal(), 'getHighestCpmBids').callsFake(() => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]]; + }); + + config.setConfig({ + testGroupId: 15 + }); + + // Standard event flow to trigger the logger + 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); + + clock.tick(2000 + 1000); // wait for SEND_TIMEOUT + + expect(requests.length).to.equal(1); // only the logger should fire + const request = requests[0]; + expect(request.url).to.equal('https://t.pubmatic.com/wl?v=1&psrc=web'); + + const data = getLoggerJsonFromRequest(request.requestBody); + + // fd should exist, but bdv (identity partners) should NOT be present + expect(data).to.have.property('fd'); + expect(data.fd.bdv).to.be.undefined; + + // restore original getUserIds to avoid side effects on other tests + namespace.getUserIds = originalGetUserIds; + }); + it('Logger: log floor fields when prebids floor shows setConfig in location property', function () { const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); BID_REQUESTED_COPY['bids'][1]['floorData']['location'] = 'fetch'; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 3f2fb212381..2ed65dd7311 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -501,6 +501,23 @@ describe('PubMatic adapter', () => { expect(imp[0].ext.pbcode).to.equal(validBidRequests[0].adUnitCode); }); + it('should not include ae or igs in imp.ext', () => { + const bidWithAe = utils.deepClone(validBidRequests[0]); + bidWithAe.ortb2Imp = bidWithAe.ortb2Imp || {}; + bidWithAe.ortb2Imp.ext = bidWithAe.ortb2Imp.ext || {}; + bidWithAe.ortb2Imp.ext.ae = 1; + bidWithAe.ortb2Imp.ext.igs = { ae: 1, biddable: 1 }; + bidWithAe.ortb2Imp.ext.paapi = { requestedSize: { width: 300, height: 250 } }; + + const req = spec.buildRequests([bidWithAe], bidderRequest); + const { imp } = req?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('ext'); + expect(imp[0].ext).to.not.have.property('ae'); + expect(imp[0].ext).to.not.have.property('igs'); + expect(imp[0].ext).to.not.have.property('paapi'); + }); + it('should add bidfloor if kadfloor is present in parameters', () => { const request = spec.buildRequests(validBidRequests, bidderRequest); const { imp } = request?.data; @@ -1174,16 +1191,6 @@ describe('PubMatic adapter', () => { }); }); - describe('FLEDGE', () => { - it('should not send imp.ext.ae when FLEDGE is disabled, ', () => { - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.have.property('imp'); - expect(request.data.imp).to.be.an('array'); - expect(request.data.imp[0]).to.have.property('ext'); - expect(request.data.imp[0].ext).to.not.have.property('ae'); - }); - }) - describe('cpm adjustment', () => { beforeEach(() => { global.cpmAdjustment = {}; diff --git a/test/spec/modules/pubmaticRtdProvider_spec.js b/test/spec/modules/pubmaticRtdProvider_spec.js index 830fd585ddc..19536b5ebd4 100644 --- a/test/spec/modules/pubmaticRtdProvider_spec.js +++ b/test/spec/modules/pubmaticRtdProvider_spec.js @@ -1,74 +1,88 @@ import { expect } from 'chai'; -import * as priceFloors from '../../../modules/priceFloors.js'; -import * as utils from '../../../src/utils.js'; -import * as suaModule from '../../../src/fpd/sua.js'; -import { config as conf } from '../../../src/config.js'; -import * as hook from '../../../src/hook.js'; -import * as prebidGlobal from '../../../src/prebidGlobal.js'; -import { - registerSubModule, pubmaticSubmodule, getFloorsConfig, fetchData, - getCurrentTimeOfDay, getBrowserType, getOs, getDeviceType, getCountry, getUtm, getBidder, _country, - _profileConfigs, _floorsData, defaultValueTemplate, withTimeout, configMerged, - getProfileConfigs, setProfileConfigs, getTargetingData -} from '../../../modules/pubmaticRtdProvider.js'; import sinon from 'sinon'; +import * as utils from '../../../src/utils.js'; +import * as pubmaticRtdProvider from '../../../modules/pubmaticRtdProvider.js'; +import { FloorProvider } from '../../../libraries/pubmaticUtils/plugins/floorProvider.js'; +import { UnifiedPricingRule } from '../../../libraries/pubmaticUtils/plugins/unifiedPricingRule.js'; describe('Pubmatic RTD Provider', () => { let sandbox; + let fetchStub; + let logErrorStub; + let originalPluginManager; + let originalConfigJsonManager; + let pluginManagerStub; + let configJsonManagerStub; beforeEach(() => { sandbox = sinon.createSandbox(); - sandbox.stub(conf, 'getConfig').callsFake(() => { - return { - floors: { - 'enforcement': { - 'floorDeals': true, - 'enforceJS': true - } - }, - realTimeData: { - auctionDelay: 100 - } - }; + fetchStub = sandbox.stub(window, 'fetch'); + logErrorStub = sinon.stub(utils, 'logError'); + + // Store original implementations + originalPluginManager = Object.assign({}, pubmaticRtdProvider.pluginManager); + originalConfigJsonManager = Object.assign({}, pubmaticRtdProvider.configJsonManager); + + // Create stubs + pluginManagerStub = { + initialize: sinon.stub().resolves(), + executeHook: sinon.stub().resolves(), + register: sinon.stub() + }; + + configJsonManagerStub = { + fetchConfig: sinon.stub().resolves(true), + getYMConfig: sinon.stub(), + getConfigByName: sinon.stub(), + country: 'IN' + }; + + // Replace exported objects with stubs + Object.keys(pluginManagerStub).forEach(key => { + pubmaticRtdProvider.pluginManager[key] = pluginManagerStub[key]; }); + + Object.keys(configJsonManagerStub).forEach(key => { + if (key === 'country') { + Object.defineProperty(pubmaticRtdProvider.configJsonManager, key, { + get: () => configJsonManagerStub[key] + }); + } else { + pubmaticRtdProvider.configJsonManager[key] = configJsonManagerStub[key]; + } + }); + + // Reset _ymConfigPromise for each test + pubmaticRtdProvider.setYmConfigPromise(Promise.resolve()); }); afterEach(() => { sandbox.restore(); - }); + logErrorStub.restore(); - describe('registerSubModule', () => { - it('should register RTD submodule provider', () => { - const submoduleStub = sinon.stub(hook, 'submodule'); - registerSubModule(); - assert(submoduleStub.calledOnceWith('realTimeData', pubmaticSubmodule)); - submoduleStub.restore(); + // Restore original implementations + Object.keys(originalPluginManager).forEach(key => { + pubmaticRtdProvider.pluginManager[key] = originalPluginManager[key]; }); - }); - describe('submodule', () => { - describe('name', () => { - it('should be pubmatic', () => { - expect(pubmaticSubmodule.name).to.equal('pubmatic'); - }); + Object.keys(originalConfigJsonManager).forEach(key => { + if (key === 'country') { + Object.defineProperty(pubmaticRtdProvider.configJsonManager, 'country', { + get: () => originalConfigJsonManager[key] + }); + } else { + pubmaticRtdProvider.configJsonManager[key] = originalConfigJsonManager[key]; + } }); }); describe('init', () => { - let logErrorStub; - let continueAuctionStub; - - const getConfig = () => ({ + const validConfig = { params: { publisherId: 'test-publisher-id', profileId: 'test-profile-id' - }, - }); - - beforeEach(() => { - logErrorStub = sandbox.stub(utils, 'logError'); - continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); - }); + } + }; it('should return false if publisherId is missing', () => { const config = { @@ -76,26 +90,33 @@ describe('Pubmatic RTD Provider', () => { profileId: 'test-profile-id' } }; - expect(pubmaticSubmodule.init(config)).to.be.false; + const result = pubmaticRtdProvider.pubmaticSubmodule.init(config); + expect(result).to.be.false; + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.LOG_PRE_FIX} Missing publisher Id.`); }); - it('should return false if profileId is missing', () => { + it('should accept numeric publisherId by converting to string', () => { const config = { params: { - publisherId: 'test-publisher-id' + publisherId: 123, + profileId: 'test-profile-id' } }; - expect(pubmaticSubmodule.init(config)).to.be.false; + const result = pubmaticRtdProvider.pubmaticSubmodule.init(config); + expect(result).to.be.true; }); - it('should accept numeric publisherId by converting to string', () => { + it('should return false if profileId is missing', () => { const config = { params: { - publisherId: 123, - profileId: 'test-profile-id' + publisherId: 'test-publisher-id' } }; - expect(pubmaticSubmodule.init(config)).to.be.true; + const result = pubmaticRtdProvider.pubmaticSubmodule.init(config); + expect(result).to.be.false; + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.LOG_PRE_FIX} Missing profile Id.`); }); it('should accept numeric profileId by converting to string', () => { @@ -105,1139 +126,199 @@ describe('Pubmatic RTD Provider', () => { profileId: 345 } }; - expect(pubmaticSubmodule.init(config)).to.be.true; + const result = pubmaticRtdProvider.pubmaticSubmodule.init(config); + expect(result).to.be.true; }); - it('should initialize successfully with valid config', () => { - expect(pubmaticSubmodule.init(getConfig())).to.be.true; - }); + it('should initialize successfully with valid config', async () => { + configJsonManagerStub.fetchConfig.resolves(true); + pluginManagerStub.initialize.resolves(); - it('should handle empty config object', () => { - expect(pubmaticSubmodule.init({})).to.be.false; - expect(logErrorStub.calledWith(sinon.match(/Missing publisher Id/))).to.be.true; - }); + const result = pubmaticRtdProvider.pubmaticSubmodule.init(validConfig); + expect(result).to.be.true; + expect(configJsonManagerStub.fetchConfig.calledOnce).to.be.true; + expect(configJsonManagerStub.fetchConfig.firstCall.args[0]).to.equal('test-publisher-id'); + expect(configJsonManagerStub.fetchConfig.firstCall.args[1]).to.equal('test-profile-id'); - it('should return false if continueAuction is not a function', () => { - continueAuctionStub.value(undefined); - expect(pubmaticSubmodule.init(getConfig())).to.be.false; - expect(logErrorStub.calledWith(sinon.match(/continueAuction is not a function/))).to.be.true; - }); - }); - - describe('getCurrentTimeOfDay', () => { - let clock; - - beforeEach(() => { - clock = sandbox.useFakeTimers(new Date('2024-01-01T12:00:00')); // Set fixed time for testing - }); - - afterEach(() => { - clock.restore(); - }); - - const testTimes = [ - { hour: 6, expected: 'morning' }, - { hour: 13, expected: 'afternoon' }, - { hour: 18, expected: 'evening' }, - { hour: 22, expected: 'night' }, - { hour: 4, expected: 'night' } - ]; - - testTimes.forEach(({ hour, expected }) => { - it(`should return ${expected} at ${hour}:00`, () => { - clock.setSystemTime(new Date().setHours(hour)); - const result = getCurrentTimeOfDay(); - expect(result).to.equal(expected); - }); - }); - }); - - describe('getBrowserType', () => { - let userAgentStub, getLowEntropySUAStub; - - const USER_AGENTS = { - chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - firefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0', - edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/91.0.864.67 Safari/537.36', - safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.6 Mobile/15E148 Safari/604.1', - ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', - opera: 'Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.16', - unknown: 'UnknownBrowser/1.0' - }; - - beforeEach(() => { - userAgentStub = sandbox.stub(navigator, 'userAgent'); - getLowEntropySUAStub = sandbox.stub(suaModule, 'getLowEntropySUA').returns(undefined); - }); - - afterEach(() => { - userAgentStub.restore(); - getLowEntropySUAStub.restore(); + // Wait for promise to resolve + await pubmaticRtdProvider.getYmConfigPromise(); + expect(pluginManagerStub.initialize.firstCall.args[0]).to.equal(pubmaticRtdProvider.configJsonManager); }); - it('should detect Chrome', () => { - userAgentStub.value(USER_AGENTS.chrome); - expect(getBrowserType()).to.equal('9'); - }); - - it('should detect Firefox', () => { - userAgentStub.value(USER_AGENTS.firefox); - expect(getBrowserType()).to.equal('12'); - }); - - it('should detect Edge', () => { - userAgentStub.value(USER_AGENTS.edge); - expect(getBrowserType()).to.equal('2'); - }); + it('should handle config fetch error gracefully', async () => { + configJsonManagerStub.fetchConfig.resolves(false); - it('should detect Internet Explorer', () => { - userAgentStub.value(USER_AGENTS.ie); - expect(getBrowserType()).to.equal('4'); - }); - - it('should detect Opera', () => { - userAgentStub.value(USER_AGENTS.opera); - expect(getBrowserType()).to.equal('3'); - }); - - it('should return 0 for unknown browser', () => { - userAgentStub.value(USER_AGENTS.unknown); - expect(getBrowserType()).to.equal('0'); - }); - - it('should return -1 when userAgent is null', () => { - userAgentStub.value(null); - expect(getBrowserType()).to.equal('-1'); - }); - }); - - describe('Utility functions', () => { - it('should set browser correctly', () => { - expect(getBrowserType()).to.be.a('string'); - }); - - it('should set OS correctly', () => { - expect(getOs()).to.be.a('string'); - }); - - it('should set device type correctly', () => { - expect(getDeviceType()).to.be.a('string'); - }); + const result = pubmaticRtdProvider.pubmaticSubmodule.init(validConfig); + expect(result).to.be.true; - it('should set time of day correctly', () => { - expect(getCurrentTimeOfDay()).to.be.a('string'); - }); - - it('should set country correctly', () => { - expect(getCountry()).to.satisfy(value => typeof value === 'string' || value === undefined); - }); - - it('should set UTM correctly', () => { - expect(getUtm()).to.be.a('string'); - expect(getUtm()).to.be.oneOf(['0', '1']); - }); - - it('should extract bidder correctly', () => { - expect(getBidder({ bidder: 'pubmatic' })).to.equal('pubmatic'); - expect(getBidder({})).to.be.undefined; - expect(getBidder(null)).to.be.undefined; - expect(getBidder(undefined)).to.be.undefined; - }); - }); - - describe('getFloorsConfig', () => { - let floorsData, profileConfigs; - let sandbox; - let logErrorStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - logErrorStub = sandbox.stub(utils, 'logError'); - floorsData = { - "currency": "USD", - "floorProvider": "PM", - "floorsSchemaVersion": 2, - "modelGroups": [ - { - "modelVersion": "M_1", - "modelWeight": 100, - "schema": { - "fields": [ - "domain" - ] - }, - "values": { - "*": 2.00 - } - } - ], - "skipRate": 0 - }; - profileConfigs = { - 'plugins': { - 'dynamicFloors': { - 'enabled': true, - 'config': { - 'enforcement': { - 'floorDeals': false, - 'enforceJS': false - }, - 'floorMin': 0.1111, - 'skipRate': 11, - 'defaultValues': { - "*|*": 0.2 - } - } - } - } + try { + await pubmaticRtdProvider.getYmConfigPromise(); + } catch (e) { + expect(e.message).to.equal('Failed to fetch configuration'); } - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return correct config structure', () => { - const result = getFloorsConfig(floorsData, profileConfigs); - - expect(result.floors).to.be.an('object'); - expect(result.floors).to.be.an('object'); - expect(result.floors).to.have.property('enforcement'); - expect(result.floors.enforcement).to.have.property('floorDeals', false); - expect(result.floors.enforcement).to.have.property('enforceJS', false); - expect(result.floors).to.have.property('floorMin', 0.1111); - - // Verify the additionalSchemaFields structure - expect(result.floors.additionalSchemaFields).to.have.all.keys([ - 'deviceType', - 'timeOfDay', - 'browser', - 'os', - 'country', - 'utm', - 'bidder' - ]); - - Object.values(result.floors.additionalSchemaFields).forEach(field => { - expect(field).to.be.a('function'); - }); - }); - - it('should return undefined when plugin is disabled', () => { - profileConfigs.plugins.dynamicFloors.enabled = false; - const result = getFloorsConfig(floorsData, profileConfigs); - - expect(result).to.equal(undefined); - }); - - it('should initialise default values to empty object when not available', () => { - profileConfigs.plugins.dynamicFloors.config.defaultValues = undefined; - floorsData = undefined; - const result = getFloorsConfig(floorsData, profileConfigs); - - expect(result.floors.data).to.have.property('currency', 'USD'); - expect(result.floors.data).to.have.property('skipRate', 11); - expect(result.floors.data.schema).to.deep.equal(defaultValueTemplate.schema); - expect(result.floors.data.value).to.deep.equal(defaultValueTemplate.value); - }); - - it('should replace skipRate from config to data when avaialble', () => { - const result = getFloorsConfig(floorsData, profileConfigs); - - expect(result.floors.data).to.have.property('skipRate', 11); - }); - - it('should not replace skipRate from config to data when not avaialble', () => { - delete profileConfigs.plugins.dynamicFloors.config.skipRate; - const result = getFloorsConfig(floorsData, profileConfigs); - - expect(result.floors.data).to.have.property('skipRate', 0); - }); - it('should maintain correct function references', () => { - const result = getFloorsConfig(floorsData, profileConfigs); - - expect(result.floors.additionalSchemaFields.deviceType).to.equal(getDeviceType); - expect(result.floors.additionalSchemaFields.timeOfDay).to.equal(getCurrentTimeOfDay); - expect(result.floors.additionalSchemaFields.browser).to.equal(getBrowserType); - expect(result.floors.additionalSchemaFields.os).to.equal(getOs); - expect(result.floors.additionalSchemaFields.country).to.equal(getCountry); - expect(result.floors.additionalSchemaFields.utm).to.equal(getUtm); - expect(result.floors.additionalSchemaFields.bidder).to.equal(getBidder); - }); - - it('should log error when profileConfigs is not an object', () => { - profileConfigs = 'invalid'; - const result = getFloorsConfig(floorsData, profileConfigs); - expect(result).to.be.undefined; - expect(logErrorStub.calledWith(sinon.match(/profileConfigs is not an object or is empty/))).to.be.true; + expect(pluginManagerStub.initialize.called).to.be.false; }); }); - describe('fetchData for configs', () => { - let logErrorStub; - let fetchStub; - let confStub; - - beforeEach(() => { - logErrorStub = sandbox.stub(utils, 'logError'); - fetchStub = sandbox.stub(window, 'fetch'); - confStub = sandbox.stub(conf, 'setConfig'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should successfully fetch profile configs', async () => { - const mockApiResponse = { - "profileName": "profie name", - "desc": "description", - "plugins": { - "dynamicFloors": { - "enabled": false - } + describe('getBidRequestData', () => { + const reqBidsConfigObj = { + ortb2Fragments: { + bidder: {} + }, + adUnits: [ + { + code: 'div-1', + bids: [{ bidder: 'pubmatic', params: {} }] + }, + { + code: 'div-2', + bids: [{ bidder: 'pubmatic', params: {} }] } - }; - - fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200 })); - - const result = await fetchData('1234', '123', 'CONFIGS'); - expect(result).to.deep.equal(mockApiResponse); - }); - - it('should log error when JSON parsing fails', async () => { - fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); - - await fetchData('1234', '123', 'CONFIGS'); - expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*CONFIGS/))).to.be.true; - }); - - it('should log error when response is not ok', async () => { - fetchStub.resolves(new Response(null, { status: 500 })); - - await fetchData('1234', '123', 'CONFIGS'); - expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*CONFIGS/))).to.be.true; - }); - - it('should log error on network failure', async () => { - fetchStub.rejects(new Error('Network Error')); - - await fetchData('1234', '123', 'CONFIGS'); - expect(logErrorStub.called).to.be.true; - expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*CONFIGS/))).to.be.true; - }); - }); - - describe('fetchData for floors', () => { - let logErrorStub; - let fetchStub; - let confStub; + ] + }; + let callback; beforeEach(() => { - logErrorStub = sandbox.stub(utils, 'logError'); - fetchStub = sandbox.stub(window, 'fetch'); - confStub = sandbox.stub(conf, 'setConfig'); - global._country = undefined; - }); - - afterEach(() => { - sandbox.restore(); + callback = sinon.stub(); + pubmaticRtdProvider.setYmConfigPromise(Promise.resolve()); }); - it('should successfully fetch and parse floor rules', async () => { - const mockApiResponse = { - data: { - currency: 'USD', - modelGroups: [], - values: {} - } - }; + it('should call pluginManager executeHook with correct parameters', (done) => { + pluginManagerStub.executeHook.resolves(); - fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200, headers: { 'country_code': 'US' } })); + pubmaticRtdProvider.pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); - const result = await fetchData('1234', '123', 'FLOORS'); - expect(result).to.deep.equal(mockApiResponse); - expect(_country).to.equal('US'); + setTimeout(() => { + expect(pluginManagerStub.executeHook.calledOnce).to.be.true; + expect(pluginManagerStub.executeHook.firstCall.args[0]).to.equal('processBidRequest'); + expect(pluginManagerStub.executeHook.firstCall.args[1]).to.deep.equal(reqBidsConfigObj); + expect(callback.calledOnce).to.be.true; + done(); + }, 0); }); - it('should correctly extract the first unique country code from response headers', async () => { - fetchStub.resolves(new Response(JSON.stringify({}), { - status: 200, - headers: { 'country_code': 'US,IN,US' } - })); + it('should add country information to ORTB2', (done) => { + pluginManagerStub.executeHook.resolves(); - await fetchData('1234', '123', 'FLOORS'); - expect(_country).to.equal('US'); - }); - - it('should set _country to undefined if country_code header is missing', async () => { - fetchStub.resolves(new Response(JSON.stringify({}), { - status: 200 - })); - - await fetchData('1234', '123', 'FLOORS'); - expect(_country).to.be.undefined; - }); + pubmaticRtdProvider.pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); - it('should log error when JSON parsing fails', async () => { - fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); - - await fetchData('1234', '123', 'FLOORS'); - expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*FLOORS/))).to.be.true; - }); - - it('should log error when response is not ok', async () => { - fetchStub.resolves(new Response(null, { status: 500 })); - - await fetchData('1234', '123', 'FLOORS'); - expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*FLOORS/))).to.be.true; - }); - - it('should log error on network failure', async () => { - fetchStub.rejects(new Error('Network Error')); - - await fetchData('1234', '123', 'FLOORS'); - expect(logErrorStub.called).to.be.true; - expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*FLOORS/))).to.be.true; - }); - }); - - describe('getBidRequestData', function () { - let callback, continueAuctionStub, mergeDeepStub, logErrorStub; - - const reqBidsConfigObj = { - adUnits: [{ code: 'ad-slot-code-0' }], - auctionId: 'auction-id-0', - ortb2Fragments: { - bidder: { + setTimeout(() => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[pubmaticRtdProvider.CONSTANTS.SUBMODULE_NAME]).to.deep.equal({ user: { ext: { - ctr: 'US', + ctr: 'IN' } } - } - } - }; - - const ortb2 = { - user: { - ext: { - ctr: 'US', - } - } - } - - const hookConfig = { - reqBidsConfigObj, - context: this, - nextFn: () => true, - haveExited: false, - timer: null - }; - - beforeEach(() => { - callback = sinon.spy(); - continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); - logErrorStub = sandbox.stub(utils, 'logError'); - - global.configMergedPromise = Promise.resolve(); - }); - - afterEach(() => { - sandbox.restore(); // Restore all stubs/spies - }); - - it('should call continueAuction with correct hookConfig', async function () { - configMerged(); - await pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); - - expect(continueAuctionStub.called).to.be.true; - expect(continueAuctionStub.firstCall.args[0]).to.have.property('reqBidsConfigObj', reqBidsConfigObj); - expect(continueAuctionStub.firstCall.args[0]).to.have.property('haveExited', false); - }); - - // it('should merge country data into ortb2Fragments.bidder', async function () { - // configMerged(); - // global._country = 'US'; - // pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); - - // expect(reqBidsConfigObj.ortb2Fragments.bidder).to.have.property('pubmatic'); - // // expect(reqBidsConfigObj.ortb2Fragments.bidder.pubmatic.user.ext.ctr).to.equal('US'); - // }); - - it('should call callback once after execution', async function () { - configMerged(); - await pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); - - expect(callback.called).to.be.true; + }); + done(); + }, 0); }); }); - describe('withTimeout', function () { - it('should resolve with the original promise value if it resolves before the timeout', async function () { - const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 50)); - const result = await withTimeout(promise, 100); - expect(result).to.equal('success'); - }); - - it('should resolve with undefined if the promise takes longer than the timeout', async function () { - const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 200)); - const result = await withTimeout(promise, 100); - expect(result).to.be.undefined; - }); - - it('should properly handle rejected promises', async function () { - const promise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failure')), 50)); - try { - await withTimeout(promise, 100); - } catch (error) { - expect(error.message).to.equal('Failure'); + describe('getTargetingData', () => { + const adUnitCodes = ['div-1', 'div-2']; + const config = { + params: { + publisherId: 'test-publisher-id', + profileId: 'test-profile-id' } - }); - - it('should resolve with undefined if the original promise is rejected but times out first', async function () { - const promise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failure')), 200)); - const result = await withTimeout(promise, 100); - expect(result).to.be.undefined; - }); + }; + const userConsent = {}; + const auction = {}; + const unifiedPricingRule = { + 'div-1': { key1: 'value1' }, + 'div-2': { key2: 'value2' } + }; - it('should clear the timeout when the promise resolves before the timeout', async function () { - const clock = sinon.useFakeTimers(); - const clearTimeoutSpy = sinon.spy(global, 'clearTimeout'); + it('should call pluginManager executeHook with correct parameters', () => { + pluginManagerStub.executeHook.returns(unifiedPricingRule); - const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 50)); - const resultPromise = withTimeout(promise, 100); + const result = pubmaticRtdProvider.getTargetingData(adUnitCodes, config, userConsent, auction); - clock.tick(50); - await resultPromise; + expect(pluginManagerStub.executeHook.calledOnce).to.be.true; + expect(pluginManagerStub.executeHook.firstCall.args[0]).to.equal('getTargeting'); + expect(pluginManagerStub.executeHook.firstCall.args[1]).to.equal(adUnitCodes); + expect(pluginManagerStub.executeHook.firstCall.args[2]).to.equal(config); + expect(pluginManagerStub.executeHook.firstCall.args[3]).to.equal(userConsent); + expect(pluginManagerStub.executeHook.firstCall.args[4]).to.equal(auction); + expect(result).to.equal(unifiedPricingRule); + }); - expect(clearTimeoutSpy.called).to.be.true; + it('should return empty object if no targeting data', () => { + pluginManagerStub.executeHook.returns({}); - clearTimeoutSpy.restore(); - clock.restore(); + const result = pubmaticRtdProvider.getTargetingData(adUnitCodes, config, userConsent, auction); + expect(result).to.deep.equal({}); }); }); - describe('getTargetingData', function () { - let sandbox; - let logInfoStub; + describe('ConfigJsonManager', () => { + let configManager; beforeEach(() => { - sandbox = sinon.createSandbox(); - logInfoStub = sandbox.stub(utils, 'logInfo'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return empty object when profileConfigs is undefined', function () { - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to undefined - setProfileConfigs(undefined); - - const adUnitCodes = ['test-ad-unit']; - const config = {}; - const userConsent = {}; - const auction = {}; - - const result = getTargetingData(adUnitCodes, config, userConsent, auction); - - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - expect(result).to.deep.equal({}); - expect(logInfoStub.calledWith(sinon.match(/pmTargetingKeys is disabled or profileConfigs is undefined/))).to.be.true; + configManager = pubmaticRtdProvider.ConfigJsonManager(); }); - it('should return empty object when pmTargetingKeys.enabled is false', function () { - // Create profileConfigs with pmTargetingKeys.enabled set to false - const profileConfigsMock = { - plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: false - } - } - } + it('should fetch config successfully', async () => { + const mockResponse = { + ok: true, + headers: { + get: sinon.stub().withArgs('country_code').returns('US') + }, + json: sinon.stub().resolves({ plugins: { test: { enabled: true } } }) }; - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - const adUnitCodes = ['test-ad-unit']; - const config = {}; - const userConsent = {}; - const auction = {}; + fetchStub.resolves(mockResponse); - const result = getTargetingData(adUnitCodes, config, userConsent, auction); + const result = await configManager.fetchConfig('pub-123', 'profile-456'); - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - expect(result).to.deep.equal({}); - expect(logInfoStub.calledWith(sinon.match(/pmTargetingKeys is disabled or profileConfigs is undefined/))).to.be.true; + expect(result).to.be.true; + expect(fetchStub.calledOnce).to.be.true; + expect(fetchStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.ENDPOINTS.BASEURL}/pub-123/profile-456/${pubmaticRtdProvider.CONSTANTS.ENDPOINTS.CONFIGS}`); + expect(configManager.country).to.equal('US'); }); - it('should set pm_ym_flrs to 0 when no RTD floor is applied to any bid', function () { - // Create profileConfigs with pmTargetingKeys.enabled set to true - const profileConfigsMock = { - plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: true - } - } - } - }; - - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - // Create multiple ad unit codes to test - const adUnitCodes = ['ad-unit-1', 'ad-unit-2']; - const config = {}; - const userConsent = {}; - - // Create a mock auction object with bids that don't have RTD floors applied - // This tests several scenarios where RTD floor is not applied: - // 1. No floorData - // 2. floorData but floorProvider is not 'PM' - // 3. floorData with floorProvider 'PM' but skipped is true - const auction = { - adUnits: [ - { - code: 'ad-unit-1', - bids: [ - { bidder: 'bidderA' }, // No floorData - { bidder: 'bidderB', floorData: { floorProvider: 'OTHER' } } // Not PM provider - ] - }, - { - code: 'ad-unit-2', - bids: [ - { bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: true } } // PM but skipped - ] - } - ], - bidsReceived: [ - { adUnitCode: 'ad-unit-1', bidder: 'bidderA' }, - { adUnitCode: 'ad-unit-1', bidder: 'bidderB', floorData: { floorProvider: 'OTHER' } }, - { adUnitCode: 'ad-unit-2', bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: true } } - ] + it('should handle missing country_code header and set country to undefined', async () => { + const mockResponse = { + ok: true, + headers: { + get: sinon.stub().withArgs('country_code').returns(null) + }, + json: sinon.stub().resolves({ plugins: { test: { enabled: true } } }) }; - const result = getTargetingData(adUnitCodes, config, userConsent, auction); + fetchStub.resolves(mockResponse); - // Restore the original value - setProfileConfigs(originalProfileConfigs); + const result = await configManager.fetchConfig('pub-123', 'profile-456'); - // Verify that for each ad unit code, only pm_ym_flrs is set to 0 - expect(result['ad-unit-1']).to.have.property('pm_ym_flrs', 0); - expect(result['ad-unit-2']).to.have.property('pm_ym_flrs', 0); + expect(result).to.be.true; + expect(fetchStub.calledOnce).to.be.true; + expect(fetchStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.ENDPOINTS.BASEURL}/pub-123/profile-456/${pubmaticRtdProvider.CONSTANTS.ENDPOINTS.CONFIGS}`); + expect(configManager.country).to.be.undefined; }); - it('should set pm_ym_flrs to 1 when RTD floor is applied to a bid', function () { - // Create profileConfigs with pmTargetingKeys.enabled set to true - const profileConfigsMock = { - plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: true - } - } - } - }; - - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - // Create multiple ad unit codes to test - const adUnitCodes = ['ad-unit-1', 'ad-unit-2']; - const config = {}; - const userConsent = {}; + it('should handle fetch errors', async () => { + fetchStub.rejects(new Error('Network error')); - // Create a mock auction object with bids that have RTD floors applied - const auction = { - adUnits: [ - { - code: 'ad-unit-1', - bids: [ - { bidder: 'bidderA', floorData: { floorProvider: 'PM', skipped: false } }, - { bidder: 'bidderB', floorData: { floorProvider: 'PM', skipped: false } } - ] - }, - { - code: 'ad-unit-2', - bids: [ - { bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: false } }, - { bidder: 'bidderD', floorData: { floorProvider: 'PM', skipped: false } } - ] - } - ], - bidsReceived: [ - { adUnitCode: 'ad-unit-1', bidder: 'bidderA', floorData: { floorProvider: 'PM', skipped: false } }, - { adUnitCode: 'ad-unit-1', bidder: 'bidderB', floorData: { floorProvider: 'PM', skipped: false } }, - { adUnitCode: 'ad-unit-2', bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: false } }, - { adUnitCode: 'ad-unit-2', bidder: 'bidderD', floorData: { floorProvider: 'PM', skipped: false } } - ] - }; + const result = await configManager.fetchConfig('pub-123', 'profile-456'); - const result = getTargetingData(adUnitCodes, config, userConsent, auction); - - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - // Verify that for each ad unit code, pm_ym_flrs is set to 1 - expect(result['ad-unit-1']).to.have.property('pm_ym_flrs', 1); - expect(result['ad-unit-2']).to.have.property('pm_ym_flrs', 1); + expect(result).to.be.null; + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching config'); }); - it('should set different targeting keys for winning bids (status 1) and floored bids (status 2)', function () { - // Create profileConfigs with pmTargetingKeys.enabled set to true - const profileConfigsMock = { + it('should get config by name', () => { + const mockConfig = { plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: true - } - } + testPlugin: { enabled: true } } }; - const mockPbjs = { - getHighestCpmBids: (adUnitCode) => { - // For div2, return a winning bid - if (adUnitCode === 'div2') { - return [{ - adUnitCode: 'div2', - cpm: 5.5, - floorData: { - floorValue: 5.0, - floorProvider: 'PM' - } - }]; - } - // For all other ad units, return empty array (no winning bids) - return []; - } - }; - - // Stub getGlobal to return our mock object - const getGlobalStub = sandbox.stub(prebidGlobal, 'getGlobal').returns(mockPbjs); - - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - // Create ad unit codes to test - const adUnitCodes = ['div2', 'div3']; - const config = {}; - const userConsent = {}; - - // Create a mock auction object with bids that have RTD floors applied - const auction = { - adUnits: [ - { code: "div2", bids: [{ floorData: { floorProvider: "PM", skipped: false } }] }, - { code: "div3", bids: [{ floorData: { floorProvider: "PM", skipped: false } }] } - ], - adUnitCodes: ["div2", "div3"], - bidsReceived: [[ - { - "bidderCode": "appnexus", - "auctionId": "a262767c-5499-4e98-b694-af36dbcb50f6", - "mediaType": "banner", - "source": "client", - "cpm": 5.5, - "adUnitCode": "div2", - "adapterCode": "appnexus", - "originalCpm": 5.5, - "floorData": { - "floorValue": 5, - "floorRule": "banner|*|*|div2|*|*|*|*|*", - "floorRuleValue": 5, - "floorCurrency": "USD", - - }, - "bidder": "appnexus", - } - ]], - bidsRejected: [ - { adUnitCode: "div3", bidder: "pubmatic", cpm: 20, floorData: { floorValue: 40 }, rejectionReason: "Bid does not meet price floor" }] - }; - - const result = getTargetingData(adUnitCodes, config, userConsent, auction); - - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - // Check the test results - - expect(result['div2']).to.have.property('pm_ym_flrs', 1); - expect(result['div2']).to.have.property('pm_ym_flrv', '5.50'); - expect(result['div2']).to.have.property('pm_ym_bid_s', 1); - - expect(result['div3']).to.have.property('pm_ym_flrs', 1); - expect(result['div3']).to.have.property('pm_ym_flrv', '32.00'); - expect(result['div3']).to.have.property('pm_ym_bid_s', 2); - getGlobalStub.restore(); - }); - - describe('should handle the no bid scenario correctly', function () { - it('should handle no bid scenario correctly', function () { - // Create profileConfigs with pmTargetingKeys enabled - const profileConfigsMock = { - plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: true, - multiplier: { - nobid: 1.2 // Explicit nobid multiplier - } - } - } - } - }; - - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - // Create ad unit codes to test - const adUnitCodes = ['Div2']; - const config = {}; - const userConsent = {}; - - // Create a mock auction with no bids but with RTD floor applied - // For this test, we'll observe what the function actually does rather than - // try to match specific multiplier values - const auction = { - "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74", - "auctionStatus": "completed", - "adUnits": [ - { - "code": "Div2", - "sizes": [[300, 250]], - "mediaTypes": { - "banner": { "sizes": [[300, 250]] } - }, - "bids": [ - { - "bidder": "pubmatic", - "params": { - "publisherId": "164392", - "adSlot": "/4374asd3431/DMDemo1@160x600" - }, - "floorData": { - "floorProvider": "PM" - } - } - ] - } - ], - "adUnitCodes": ["Div2"], - "bidderRequests": [ - { - "bidderCode": "pubmatic", - "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74", - "bids": [ - { - "bidder": "pubmatic", - "adUnitCode": "Div2", - "floorData": { - "floorProvider": "PM" - }, - "mediaTypes": { - "banner": { "sizes": [[300, 250]] } - }, - "getFloor": () => { return { floor: 0.05, currency: 'USD' }; } - } - ] - } - ], - "noBids": [ - { - "bidder": "pubmatic", - "adUnitCode": "Div2", - "floorData": { - "floorProvider": "PM", - "floorMin": 0.05 - } - } - ], - "bidsReceived": [], - "bidsRejected": [], - "winningBids": [] - }; - - const result = getTargetingData(adUnitCodes, config, userConsent, auction); - - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - // Verify correct values for no bid scenario - expect(result['Div2']['pm_ym_flrs']).to.equal(1); // RTD floor was applied - expect(result['Div2']['pm_ym_bid_s']).to.equal(0); // NOBID status - - // Since finding floor values from bidder requests depends on implementation details - // we'll just verify the type rather than specific value - expect(result['Div2']['pm_ym_flrv']).to.be.a('string'); - }); - - it('should handle no bid scenario correctly for single ad unit multiple size scenarios', function () { - // Create profileConfigs with pmTargetingKeys enabled - const profileConfigsMock = { - plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: true, - multiplier: { - nobid: 1.2 // Explicit nobid multiplier - } - } - } - } - }; - - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - // Create ad unit codes to test - const adUnitCodes = ['Div2']; - const config = {}; - const userConsent = {}; - - // Create a mock auction with no bids but with RTD floor applied - // For this test, we'll observe what the function actually does rather than - // try to match specific multiplier values - const auction = { - "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74", - "auctionStatus": "completed", - "adUnits": [ - { - "code": "Div2", - "sizes": [[300, 250]], - "mediaTypes": { "banner": { "sizes": [[300, 250]] } }, - "bids": [ - { - "bidder": "pubmatic", - "params": { - "publisherId": "164392", - "adSlot": "/4374asd3431/DMDemo1@160x600" - }, - "floorData": { - "floorProvider": "PM" - } - } - ] - } - ], - "adUnitCodes": ["Div2"], - "bidderRequests": [ - { - "bidderCode": "pubmatic", - "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74", - "bids": [ - { - "bidder": "pubmatic", - "adUnitCode": "Div2", - "floorData": { - "floorProvider": "PM" - }, - "mediaTypes": { - "banner": { "sizes": [[300, 250]] } - }, - "getFloor": () => { return { floor: 5, currency: 'USD' }; } - } - ] - } - ], - "noBids": [ - { - "bidder": "pubmatic", - "adUnitCode": "Div2", - "floorData": { - "floorProvider": "PM", - "floorMin": 0.05 - } - } - ], - "bidsReceived": [], - "bidsRejected": [], - "winningBids": [] - }; - - const result = getTargetingData(adUnitCodes, config, userConsent, auction); - - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - // Verify correct values for no bid scenario - expect(result['Div2']['pm_ym_flrs']).to.equal(1); // RTD floor was applied - expect(result['Div2']['pm_ym_bid_s']).to.equal(0); // NOBID status - - // Since finding floor values from bidder requests depends on implementation details - // we'll just verify the type rather than specific value - expect(result['Div2']['pm_ym_flrv']).to.be.a('string'); - expect(result['Div2']['pm_ym_flrv']).to.equal("6.00"); - }); - - it('should handle no bid scenario correctly for multi-format ad unit with different floors', function () { - // Create profileConfigs with pmTargetingKeys enabled - const profileConfigsMock = { - plugins: { - dynamicFloors: { - pmTargetingKeys: { - enabled: true, - multiplier: { - nobid: 1.2 // Explicit nobid multiplier - } - } - } - } - }; - - // Store the original value to restore it later - const originalProfileConfigs = getProfileConfigs(); - // Set profileConfigs to our mock - setProfileConfigs(profileConfigsMock); - - // Create ad unit codes to test - const adUnitCodes = ['multiFormatDiv']; - const config = {}; - const userConsent = {}; - - // Mock getFloor implementation that returns different floors for different media types - const mockGetFloor = (params) => { - const floors = { - 'banner': 0.50, // Higher floor for banner - 'video': 0.25 // Lower floor for video - }; - - return { - floor: floors[params.mediaType] || 0.10, - currency: 'USD' - }; - }; - - // Create a mock auction with a multi-format ad unit (banner + video) - const auction = { - "auctionId": "multi-format-test-auction", - "auctionStatus": "completed", - "adUnits": [ - { - "code": "multiFormatDiv", - "mediaTypes": { - "banner": { - "sizes": [[300, 250], [300, 600]] - }, - "video": { - "playerSize": [[640, 480]], - "context": "instream" - } - }, - "bids": [ - { - "bidder": "pubmatic", - "params": { - "publisherId": "test-publisher", - "adSlot": "/test/slot" - }, - "floorData": { - "floorProvider": "PM" - } - } - ] - } - ], - "adUnitCodes": ["multiFormatDiv"], - "bidderRequests": [ - { - "bidderCode": "pubmatic", - "auctionId": "multi-format-test-auction", - "bids": [ - { - "bidder": "pubmatic", - "adUnitCode": "multiFormatDiv", - "mediaTypes": { - "banner": { - "sizes": [[300, 250], [300, 600]] - }, - "video": { - "playerSize": [[640, 480]], - "context": "instream" - } - }, - "floorData": { - "floorProvider": "PM" - }, - "getFloor": mockGetFloor - } - ] - } - ], - "noBids": [ - { - "bidder": "pubmatic", - "adUnitCode": "multiFormatDiv", - "floorData": { - "floorProvider": "PM" - } - } - ], - "bidsReceived": [], - "bidsRejected": [], - "winningBids": [] - }; - - // Create a spy to monitor the getFloor calls - const getFloorSpy = sinon.spy(auction.bidderRequests[0].bids[0], "getFloor"); - - // Run the targeting function - const result = getTargetingData(adUnitCodes, config, userConsent, auction); - - // Restore the original value - setProfileConfigs(originalProfileConfigs); - - // Verify correct values for no bid scenario - expect(result['multiFormatDiv']['pm_ym_flrs']).to.equal(1); // RTD floor was applied - expect(result['multiFormatDiv']['pm_ym_bid_s']).to.equal(0); // NOBID status - - // Verify that getFloor was called with both media types - expect(getFloorSpy.called).to.be.true; - let bannerCallFound = false; - let videoCallFound = false; - - getFloorSpy.getCalls().forEach(call => { - const args = call.args[0]; - if (args.mediaType === 'banner') bannerCallFound = true; - if (args.mediaType === 'video') videoCallFound = true; - }); - - expect(bannerCallFound).to.be.true; // Verify banner format was checked - expect(videoCallFound).to.be.true; // Verify video format was checked - - // Since we created the mockGetFloor to return 0.25 for video (lower than 0.50 for banner), - // we expect the RTD provider to use the minimum floor value (0.25) - // We can't test the exact value due to multiplier application, but we can make sure - // it's derived from the lower value - expect(parseFloat(result['multiFormatDiv']['pm_ym_flrv'])).to.be.closeTo(0.25 * 1.2, 0.001); // 0.25 * nobid multiplier (1.2) + configManager.setYMConfig(mockConfig); - // Clean up - getFloorSpy.restore(); - }); + const result = configManager.getConfigByName('testPlugin'); + expect(result).to.deep.equal({ enabled: true }); }); }); }); diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js index 786f6a98b5c..37aaa964602 100644 --- a/test/spec/modules/pubriseBidAdapter_spec.js +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -482,7 +482,7 @@ describe('PubriseBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -491,9 +491,7 @@ describe('PubriseBidAdapter', function () { 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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -502,7 +500,7 @@ describe('PubriseBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/pubstackBidAdapter_spec.js b/test/spec/modules/pubstackBidAdapter_spec.js new file mode 100644 index 00000000000..1f8143e47fa --- /dev/null +++ b/test/spec/modules/pubstackBidAdapter_spec.js @@ -0,0 +1,348 @@ +import { expect } from 'chai'; +import { spec } from 'modules/pubstackBidAdapter'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { hook } from 'src/hook.js'; +import 'src/prebid.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; + +describe('pubstackBidAdapter', function () { + const baseBidRequest = { + adUnitCode: 'adunit-code', + auctionId: 'auction-1', + bidId: 'bid-1', + bidder: 'pubstack', + bidderRequestId: 'request-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + siteId: 'site-123', + adUnitName: 'adunit-1' + }, + sizes: [[300, 250]], + transactionId: 'transaction-1' + }; + + const baseBidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consent-string', + vendorData: { + purpose: { + consents: { 1: true } + } + } + }, + uspConsent: '1YYN', + gppConsent: { + gppString: 'gpp-string', + applicableSections: [7, 8] + }, + refererInfo: { + referer: 'https://example.com' + } + }; + + const clone = (obj) => JSON.parse(JSON.stringify(obj)); + + const createBidRequest = (overrides = {}) => { + const bidRequest = clone(baseBidRequest); + const { params = {}, ...otherOverrides } = overrides; + Object.assign(bidRequest, otherOverrides); + bidRequest.params = { + ...bidRequest.params, + ...params + }; + return bidRequest; + }; + + const createBidderRequest = (bidRequest, overrides = {}) => ({ + ...clone(baseBidderRequest), + bids: [bidRequest], + ...overrides + }); + + const extractBids = (result) => Array.isArray(result) ? result : result?.bids; + + const findSyncForSite = (syncs, siteId) => + syncs.find((sync) => new URL(sync.url).searchParams.get('siteId') === siteId); + + const getDecodedSyncPayload = (sync) => + JSON.parse(atob(new URL(sync.url).searchParams.get('consent'))); + + before(() => { + hook.ready(); + }); + + beforeEach(function () { + config.resetConfig(); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('isBidRequestValid', function () { + it('returns true when required params are present', function () { + expect(spec.isBidRequestValid(createBidRequest())).to.equal(true); + }); + + it('returns false for invalid params when debug is disabled', function () { + config.setConfig({ debug: false }); + expect(spec.isBidRequestValid(createBidRequest({ params: { siteId: undefined } }))).to.equal(false); + expect(spec.isBidRequestValid(createBidRequest({ params: { adUnitName: undefined } }))).to.equal(false); + }); + + it('returns true for invalid params when debug is enabled', function () { + config.setConfig({ debug: true }); + expect(spec.isBidRequestValid(createBidRequest({ params: { siteId: undefined } }))).to.equal(true); + expect(spec.isBidRequestValid(createBidRequest({ params: { adUnitName: undefined } }))).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('builds a POST request with ORTB data and bidder extensions', function () { + const bidRequest = createBidRequest(); + const bidderRequest = createBidderRequest(bidRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://node.pbstck.com/openrtb2/auction?siteId=site-123'); + expect(utils.deepAccess(request, 'data.site.publisher.id')).to.equal('site-123'); + expect(utils.deepAccess(request, 'data.test')).to.equal(0); + expect(request.data.imp).to.have.lengthOf(1); + expect(utils.deepAccess(request, 'data.imp.0.id')).to.equal('bid-1'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.bidder.pubstack.adUnitName')).to.equal('adunit-1'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.code')).to.equal('adunit-code'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.viewability')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.viewportDistance')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.height')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.version')).to.be.a('string'); + expect(utils.deepAccess(request, 'data.ext.prebid.request.count')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.request.timeoutCount')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.tabActive')).to.be.a('boolean'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.height')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.viewportHeight')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.timeFromNavigation')).to.be.a('number'); + }); + + it('sets test to 1 when prebid debug mode is enabled', function () { + config.setConfig({ debug: true }); + const bidRequest = createBidRequest({ bidId: 'bid-debug' }); + const bidderRequest = createBidderRequest(bidRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); + + expect(utils.deepAccess(request, 'data.test')).to.equal(1); + }); + + it('increments request counter for each call', function () { + const firstBidRequest = createBidRequest({ bidId: 'bid-counter-1' }); + const firstRequest = spec.buildRequests([firstBidRequest], createBidderRequest(firstBidRequest)); + const secondBidRequest = createBidRequest({ + bidId: 'bid-counter-2', + adUnitCode: 'adunit-code-2', + params: { adUnitName: 'adunit-2' } + }); + const secondRequest = spec.buildRequests([secondBidRequest], createBidderRequest(secondBidRequest)); + + expect(utils.deepAccess(secondRequest, 'data.ext.prebid.request.count')) + .to.equal(utils.deepAccess(firstRequest, 'data.ext.prebid.request.count') + 1); + }); + + it('updates timeout count after onTimeout callback', function () { + const bidRequest = createBidRequest({ bidId: 'bid-timeout-rate-1' }); + const firstRequest = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + expect(utils.deepAccess(firstRequest, 'data.ext.prebid.request.timeoutCount')).to.equal(0); + + spec.onTimeout([]); + + const secondBidRequest = createBidRequest({ bidId: 'bid-timeout-rate-2' }); + const secondRequest = spec.buildRequests([secondBidRequest], createBidderRequest(secondBidRequest)); + expect(utils.deepAccess(secondRequest, 'data.ext.prebid.request.timeoutCount')).to.equal(1); + }); + }); + + describe('interpretResponse', function () { + it('returns empty array when response has no body', function () { + const bidRequest = createBidRequest(); + const request = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + const bids = spec.interpretResponse({ body: null }, request); + expect(bids).to.be.an('array'); + expect(bids).to.have.lengthOf(0); + }); + + it('maps ORTB bid responses into prebid bids', function () { + const bidRequest = createBidRequest(); + const request = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + const serverResponse = { + body: { + id: 'resp-1', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-1', + mtype: 1, + price: 1.23, + w: 300, + h: 250, + adm: '
ad
', + crid: 'creative-1' + } + ] + } + ] + } + }; + + const result = spec.interpretResponse(serverResponse, request); + const bids = extractBids(result); + expect(bids).to.have.lengthOf(1); + expect(bids[0]).to.include({ + requestId: 'bid-1', + cpm: 1.23, + width: 300, + height: 250, + ad: '
ad
', + creativeId: 'creative-1' + }); + expect(bids[0]).to.have.property('currency', 'USD'); + }); + + it('returns no bids when ORTB response impid does not match request imp ids', function () { + const bidRequest = createBidRequest({ bidId: 'bid-match-required' }); + const request = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: 'unknown-imp-id', + price: 2.5, + w: 300, + h: 250, + adm: '
ad
', + crid: 'creative-unknown' + }] + }] + } + }; + + expect(extractBids(spec.interpretResponse(serverResponse, request))).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('returns iframe sync with encoded consent payload and site id', function () { + const bidRequest = createBidRequest(); + const bidderRequest = createBidderRequest(bidRequest); + spec.buildRequests([bidRequest], bidderRequest); + + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: true }, + [], + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + + const siteSync = findSyncForSite(syncs, 'site-123'); + expect(siteSync).to.not.equal(undefined); + expect(siteSync.type).to.equal('iframe'); + expect(siteSync.url).to.include('https://cdn.pbstck.com/async_usersync.html'); + + const consentPayload = getDecodedSyncPayload(siteSync); + expect(consentPayload).to.deep.equal({ + gdprConsentString: 'consent-string', + gdprApplies: true, + uspConsent: '1YYN', + gpp: 'gpp-string', + gpp_sid: [7, 8] + }); + }); + + it('returns image sync when iframe sync is disabled', function () { + const bidRequest = createBidRequest({ bidId: 'bid-pixel' }); + const bidderRequest = createBidderRequest(bidRequest); + spec.buildRequests([bidRequest], bidderRequest); + + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: true }, + [], + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + + const siteSync = findSyncForSite(syncs, 'site-123'); + expect(siteSync).to.not.equal(undefined); + expect(siteSync.type).to.equal('image'); + expect(siteSync.url).to.include('https://cdn.pbstck.com/async_usersync.png'); + }); + + it('returns no syncs when both iframe and pixel sync are disabled', function () { + const bidRequest = createBidRequest({ bidId: 'bid-disabled-syncs' }); + const bidderRequest = createBidderRequest(bidRequest); + spec.buildRequests([bidRequest], bidderRequest); + + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: false }, + [], + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + + expect(syncs).to.deep.equal([]); + }); + + it('includes sync entries for each seen site id', function () { + const bidA = createBidRequest({ + bidId: 'bid-site-a', + adUnitCode: 'ad-site-a', + params: { siteId: 'site-a', adUnitName: 'adunit-a' } + }); + const bidB = createBidRequest({ + bidId: 'bid-site-b', + adUnitCode: 'ad-site-b', + params: { siteId: 'site-b', adUnitName: 'adunit-b' } + }); + + spec.buildRequests([bidA], createBidderRequest(bidA)); + spec.buildRequests([bidB], createBidderRequest(bidB)); + + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + baseBidderRequest.gdprConsent, + baseBidderRequest.uspConsent, + baseBidderRequest.gppConsent + ); + const siteIds = syncs.map((sync) => new URL(sync.url).searchParams.get('siteId')); + + expect(siteIds).to.include('site-a'); + expect(siteIds).to.include('site-b'); + }); + + it('supports null consent objects in the sync payload', function () { + const bidRequest = createBidRequest({ + bidId: 'bid-null-consent', + params: { siteId: 'site-null-consent', adUnitName: 'adunit-null-consent' } + }); + spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + null, + null, + null + ); + + const siteSync = findSyncForSite(syncs, 'site-null-consent'); + expect(siteSync).to.not.equal(undefined); + expect(getDecodedSyncPayload(siteSync)).to.deep.equal({ uspConsent: null }); + }); + }); +}); diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js index 279962d0d3c..b2b7511cb18 100644 --- a/test/spec/modules/qtBidAdapter_spec.js +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -481,7 +481,7 @@ describe('QTBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -490,9 +490,7 @@ describe('QTBidAdapter', function () { 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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -501,7 +499,7 @@ describe('QTBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js index e6cdb12e31d..9b3dd70f1a6 100644 --- a/test/spec/modules/rakutenBidAdapter_spec.js +++ b/test/spec/modules/rakutenBidAdapter_spec.js @@ -161,7 +161,7 @@ describe('rakutenBidAdapter', function() { pixelEnabled: true } }); - it('sucess usersync url', function () { + it('success usersync url', function () { const result = []; result.push({type: 'image', url: 'https://rdn1.test/sync?uid=9876543210'}); result.push({type: 'image', url: 'https://rdn2.test/sync?uid=9876543210'}); diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index 883e8bcc3c7..1e8a6d53993 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -7,13 +7,14 @@ import 'src/prebid.js'; import {attachRealTimeDataProvider, onDataDeletionRequest} from 'modules/rtdModule/index.js'; import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; import {MODULE_TYPE_RTD} from '../../../src/activities/modules.js'; - -const getBidRequestDataSpy = sinon.spy(); +import {registerActivityControl} from '../../../src/activities/rules.js'; +import {ACTIVITY_ENRICH_UFPD, ACTIVITY_TRANSMIT_EIDS} from '../../../src/activities/activities.js'; describe('Real time module', function () { let eventHandlers; let sandbox; let validSM, validSMWait, invalidSM, failureSM, nonConfSM, conf; + let getBidRequestDataStub; function mockEmitEvent(event, ...args) { (eventHandlers[event] || []).forEach((h) => h(...args)); @@ -22,6 +23,8 @@ describe('Real time module', function () { before(() => { eventHandlers = {}; sandbox = sinon.createSandbox(); + getBidRequestDataStub = sinon.stub(); + sandbox.stub(events, 'on').callsFake((event, handler) => { if (!eventHandlers.hasOwnProperty(event)) { eventHandlers[event] = []; @@ -41,7 +44,7 @@ describe('Real time module', function () { getTargetingData: (adUnitsCodes) => { return {'ad2': {'key': 'validSM'}} }, - getBidRequestData: getBidRequestDataSpy + getBidRequestData: getBidRequestDataStub }; validSMWait = { @@ -50,7 +53,7 @@ describe('Real time module', function () { getTargetingData: (adUnitsCodes) => { return {'ad1': {'key': 'validSMWait'}} }, - getBidRequestData: getBidRequestDataSpy + getBidRequestData: getBidRequestDataStub }; invalidSM = { @@ -112,18 +115,27 @@ describe('Real time module', function () { }) describe('', () => { - let PROVIDERS, _detachers; + let PROVIDERS, _detachers, rules; beforeEach(function () { PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; _detachers = PROVIDERS.map(rtdModule.attachRealTimeDataProvider); rtdModule.init(config); config.setConfig(conf); + rules = [ + registerActivityControl(ACTIVITY_TRANSMIT_EIDS, 'test', (params) => { + return {allow: false}; + }), + registerActivityControl(ACTIVITY_ENRICH_UFPD, 'test', (params) => { + return {allow: false}; + }) + ] }); afterEach(function () { _detachers.forEach((f) => f()); config.resetConfig(); + rules.forEach(rule => rule()); }); it('should use only valid modules', function () { @@ -131,11 +143,49 @@ describe('Real time module', function () { }); it('should be able to modify bid request', function (done) { + const request = {bidRequest: {}}; + getBidRequestDataStub.callsFake((req) => { + req.foo = 'bar'; + }); + rtdModule.setBidRequestsData(() => { + assert(getBidRequestDataStub.calledTwice); + assert(getBidRequestDataStub.calledWith(sinon.match({bidRequest: {}}))); + expect(request.foo).to.eql('bar'); + done(); + }, request) + }); + + it('should apply guard to modules, but not affect ortb2Fragments otherwise', (done) => { + const ortb2Fragments = { + global: { + user: { + eids: ['id'] + } + }, + bidder: { + bidderA: { + user: { + eids: ['bid'] + } + } + } + }; + const request = {ortb2Fragments}; + getBidRequestDataStub.callsFake((req) => { + expect(req.ortb2Fragments.global.user.eids).to.not.exist; + expect(req.ortb2Fragments.bidder.bidderA.eids).to.not.exist; + req.ortb2Fragments.global.user.yob = 123; + req.ortb2Fragments.bidder.bidderB = { + user: { + yob: 123 + } + }; + }); rtdModule.setBidRequestsData(() => { - assert(getBidRequestDataSpy.calledTwice); - assert(getBidRequestDataSpy.calledWith(sinon.match({bidRequest: {}}))); + expect(request.ortb2Fragments.global.user.eids).to.eql(['id']); + expect(request.ortb2Fragments.bidder.bidderB?.user?.yob).to.not.exist; done(); - }, {bidRequest: {}}) + }, request); }); it('sould place targeting on adUnits', function (done) { diff --git a/test/spec/modules/revantageBidAdapter_spec.js b/test/spec/modules/revantageBidAdapter_spec.js new file mode 100644 index 00000000000..b560c218bfd --- /dev/null +++ b/test/spec/modules/revantageBidAdapter_spec.js @@ -0,0 +1,994 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec } from '../../../modules/revantageBidAdapter.js'; +import { newBidder } from '../../../src/adapters/bidderFactory.js'; +import { deepClone } from '../../../src/utils.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from '../../../src/utils.js'; + +const ENDPOINT_URL = 'https://bid.revantage.io/bid'; +const SYNC_URL = 'https://sync.revantage.io/sync'; + +describe('RevantageBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'revantage', + params: { + feedId: 'test-feed-123' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid is undefined', function () { + expect(spec.isBidRequestValid(undefined)).to.equal(false); + }); + + it('should return false when bid is null', function () { + expect(spec.isBidRequestValid(null)).to.equal(false); + }); + + it('should return false when params is missing', function () { + const invalidBid = deepClone(validBid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when feedId is missing', function () { + const invalidBid = deepClone(validBid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when feedId is empty string', function () { + const invalidBid = deepClone(validBid); + invalidBid.params = { feedId: '' }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return true with optional params', function () { + const bidWithOptional = deepClone(validBid); + bidWithOptional.params.placementId = 'test-placement'; + bidWithOptional.params.publisherId = 'test-publisher'; + expect(spec.isBidRequestValid(bidWithOptional)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const validBidRequests = [{ + bidder: 'revantage', + params: { + feedId: 'test-feed-123', + placementId: 'test-placement', + publisherId: 'test-publisher' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor: function(params) { + return { + currency: 'USD', + floor: 0.5 + }; + } + }]; + + const bidderRequest = { + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gdprApplies: true + }, + uspConsent: '1---', + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7, 8] + }, + ortb2: { + site: { + domain: 'example.com', + page: 'https://example.com/test' + }, + device: { + ua: 'Mozilla/5.0...', + language: 'en' + } + } + }; + + it('should return valid request object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.include(ENDPOINT_URL); + expect(request.url).to.include('feed=test-feed-123'); + expect(request.options.contentType).to.equal('text/plain'); + expect(request.options.withCredentials).to.equal(false); + expect(request.bidRequests).to.equal(validBidRequests); + }); + + it('should include all required OpenRTB fields', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.id).to.equal('1d1a030790a475'); + expect(data.imp).to.be.an('array').with.lengthOf(1); + expect(data.site).to.be.an('object'); + expect(data.device).to.be.an('object'); + expect(data.user).to.be.an('object'); + expect(data.regs).to.be.an('object'); + expect(data.cur).to.deep.equal(['USD']); + expect(data.tmax).to.equal(3000); + }); + + it('should build correct impression object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.id).to.equal('30b31c1838de1e'); + expect(imp.tagid).to.equal('adunit-code'); + expect(imp.bidfloor).to.equal(0.5); + expect(imp.banner).to.be.an('object'); + expect(imp.banner.w).to.equal(300); + expect(imp.banner.h).to.equal(250); + expect(imp.banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 300, h: 600 } + ]); + }); + + it('should include bidder-specific ext parameters', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.ext.feedId).to.equal('test-feed-123'); + expect(imp.ext.bidder.placementId).to.equal('test-placement'); + expect(imp.ext.bidder.publisherId).to.equal('test-publisher'); + }); + + it('should include GDPR consent data', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + }); + + it('should include CCPA/USP consent', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.regs.ext.us_privacy).to.equal('1---'); + }); + + it('should include GPP consent with sections as array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.regs.ext.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.ext.gpp_sid).to.deep.equal([7, 8]); + }); + + it('should handle GDPR not applies', function () { + const bidderRequestNoGdpr = deepClone(bidderRequest); + bidderRequestNoGdpr.gdprConsent.gdprApplies = false; + + const request = spec.buildRequests(validBidRequests, bidderRequestNoGdpr); + const data = JSON.parse(request.data); + + expect(data.regs.ext.gdpr).to.equal(0); + }); + + it('should handle missing getFloor function', function () { + const bidRequestsWithoutFloor = deepClone(validBidRequests); + delete bidRequestsWithoutFloor[0].getFloor; + + const request = spec.buildRequests(bidRequestsWithoutFloor, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp[0].bidfloor).to.equal(0); + }); + + it('should handle getFloor returning non-USD currency', function () { + const bidRequestsEurFloor = deepClone(validBidRequests); + bidRequestsEurFloor[0].getFloor = function() { + return { currency: 'EUR', floor: 0.5 }; + }; + + const request = spec.buildRequests(bidRequestsEurFloor, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp[0].bidfloor).to.equal(0); + }); + + it('should handle missing ortb2 data', function () { + const bidderRequestNoOrtb2 = deepClone(bidderRequest); + delete bidderRequestNoOrtb2.ortb2; + + const request = spec.buildRequests(validBidRequests, bidderRequestNoOrtb2); + const data = JSON.parse(request.data); + + expect(data.site).to.be.an('object'); + expect(data.site.domain).to.exist; + expect(data.device).to.be.an('object'); + }); + + it('should include supply chain when present in bidderRequest', function () { + const bidderRequestWithSchain = deepClone(bidderRequest); + bidderRequestWithSchain.schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '12345', + hp: 1 + }] + }; + + const request = spec.buildRequests(validBidRequests, bidderRequestWithSchain); + const data = JSON.parse(request.data); + + expect(data.schain).to.exist; + expect(data.schain.ver).to.equal('1.0'); + expect(data.schain.complete).to.equal(1); + expect(data.schain.nodes).to.have.lengthOf(1); + }); + + it('should include supply chain from first bid request', function () { + const bidRequestsWithSchain = deepClone(validBidRequests); + bidRequestsWithSchain[0].schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'bidder.com', sid: '999', hp: 1 }] + }; + + const bidderRequestNoSchain = deepClone(bidderRequest); + delete bidderRequestNoSchain.schain; + + const request = spec.buildRequests(bidRequestsWithSchain, bidderRequestNoSchain); + const data = JSON.parse(request.data); + + expect(data.schain).to.exist; + expect(data.schain.nodes[0].asi).to.equal('bidder.com'); + }); + + it('should include user EIDs when present', function () { + const bidRequestsWithEids = deepClone(validBidRequests); + bidRequestsWithEids[0].userIdAsEids = [ + { + source: 'id5-sync.com', + uids: [{ id: 'test-id5-id', atype: 1 }] + } + ]; + + const request = spec.buildRequests(bidRequestsWithEids, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.user.eids).to.be.an('array'); + expect(data.user.eids[0].source).to.equal('id5-sync.com'); + }); + + it('should return empty array when feedIds differ across bids', function () { + const mixedFeedBidRequests = [ + { + bidder: 'revantage', + params: { feedId: 'feed-1' }, + adUnitCode: 'adunit-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + sizes: [[300, 250]], + bidId: 'bid1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }, + { + bidder: 'revantage', + params: { feedId: 'feed-2' }, + adUnitCode: 'adunit-2', + mediaTypes: { banner: { sizes: [[728, 90]] } }, + sizes: [[728, 90]], + bidId: 'bid2', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + } + ]; + + const request = spec.buildRequests(mixedFeedBidRequests, bidderRequest); + expect(request).to.deep.equal([]); + }); + + it('should return empty array on exception', function () { + const request = spec.buildRequests(null, bidderRequest); + expect(request).to.deep.equal([]); + }); + + it('should handle video media type', function () { + const videoBidRequests = [{ + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'video-adunit', + bidId: 'video-bid-1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4', 'video/webm'], + protocols: [2, 3, 5, 6], + api: [1, 2], + placement: 1, + minduration: 5, + maxduration: 30, + skip: 1, + skipmin: 5, + skipafter: 5 + } + } + }]; + + const request = spec.buildRequests(videoBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.video).to.exist; + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.mimes).to.deep.equal(['video/mp4', 'video/webm']); + expect(imp.video.protocols).to.deep.equal([2, 3, 5, 6]); + expect(imp.video.minduration).to.equal(5); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.skip).to.equal(1); + expect(imp.banner).to.be.undefined; + }); + + it('should handle multi-format (banner + video) bid', function () { + const multiFormatBidRequests = [{ + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'multi-format-adunit', + bidId: 'multi-bid-1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'] + } + } + }]; + + const request = spec.buildRequests(multiFormatBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.banner).to.exist; + expect(imp.video).to.exist; + }); + + it('should handle multiple impressions with same feedId', function () { + const multipleBidRequests = [ + { + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'adunit-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + sizes: [[300, 250]], + bidId: 'bid1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }, + { + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'adunit-2', + mediaTypes: { banner: { sizes: [[728, 90]] } }, + sizes: [[728, 90]], + bidId: 'bid2', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + } + ]; + + const request = spec.buildRequests(multipleBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp).to.have.lengthOf(2); + expect(data.imp[0].id).to.equal('bid1'); + expect(data.imp[1].id).to.equal('bid2'); + }); + + it('should use default sizes when sizes array is empty', function () { + const bidWithEmptySizes = [{ + bidder: 'revantage', + params: { feedId: 'test-feed' }, + adUnitCode: 'adunit-code', + mediaTypes: { banner: { sizes: [] } }, + sizes: [], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }]; + + const request = spec.buildRequests(bidWithEmptySizes, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + }); + + it('should include prebid version in ext', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.ext).to.exist; + expect(data.ext.prebid).to.exist; + expect(data.ext.prebid.version).to.exist; + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + id: '1d1a030790a475', + seatbid: [{ + seat: 'test-dsp', + bid: [{ + id: 'test-bid-id', + impid: '30b31c1838de1e', + price: 1.25, + crid: 'test-creative-123', + adm: '
Test Ad Markup
', + w: 300, + h: 250, + adomain: ['advertiser.com'], + dealid: 'deal-123' + }] + }], + cur: 'USD' + } + }; + + const bidRequest = { + bidRequests: [{ + bidId: '30b31c1838de1e', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }] + }; + + it('should return valid banner bid response', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + + expect(result).to.be.an('array').with.lengthOf(1); + + const bid = result[0]; + expect(bid.requestId).to.equal('30b31c1838de1e'); + expect(bid.cpm).to.equal(1.25); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('test-creative-123'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.ad).to.equal('
Test Ad Markup
'); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.dealId).to.equal('deal-123'); + }); + + it('should include meta data in bid response', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + const bid = result[0]; + + expect(bid.meta).to.be.an('object'); + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(bid.meta.dsp).to.equal('test-dsp'); + expect(bid.meta.networkName).to.equal('Revantage'); + }); + + it('should include burl when provided', function () { + const responseWithBurl = deepClone(serverResponse); + responseWithBurl.body.seatbid[0].bid[0].burl = 'https://bid.revantage.io/win?auction=1d1a030790a475&dsp=test-dsp&price=0.625000&impid=30b31c1838de1e&bidid=test-bid-id&adid=test-creative-123&page=&domain=&country=&feedid=test-feed&ref='; + + const result = spec.interpretResponse(responseWithBurl, bidRequest); + const bid = result[0]; + + expect(bid.burl).to.include('https://bid.revantage.io/win'); + expect(bid.burl).to.include('dsp=test-dsp'); + expect(bid.burl).to.include('impid=30b31c1838de1e'); + }); + + it('should handle video response with vastXml', function () { + const videoResponse = deepClone(serverResponse); + videoResponse.body.seatbid[0].bid[0].vastXml = '...'; + delete videoResponse.body.seatbid[0].bid[0].adm; + + const videoBidRequest = { + bidRequests: [{ + bidId: '30b31c1838de1e', + adUnitCode: 'video-adunit', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + } + }] + }; + + const result = spec.interpretResponse(videoResponse, videoBidRequest); + const bid = result[0]; + + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.vastXml).to.equal('...'); + }); + + it('should handle video response with vastUrl', function () { + const videoResponse = deepClone(serverResponse); + videoResponse.body.seatbid[0].bid[0].vastUrl = 'https://vast.example.com/vast.xml'; + delete videoResponse.body.seatbid[0].bid[0].adm; + + const videoBidRequest = { + bidRequests: [{ + bidId: '30b31c1838de1e', + adUnitCode: 'video-adunit', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + } + }] + }; + + const result = spec.interpretResponse(videoResponse, videoBidRequest); + const bid = result[0]; + + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.vastUrl).to.equal('https://vast.example.com/vast.xml'); + }); + + it('should detect video from ext.mediaType', function () { + const videoResponse = deepClone(serverResponse); + videoResponse.body.seatbid[0].bid[0].adm = '...'; + videoResponse.body.seatbid[0].bid[0].ext = { mediaType: 'video' }; + + const result = spec.interpretResponse(videoResponse, bidRequest); + const bid = result[0]; + + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.vastXml).to.equal('...'); + }); + + it('should use default dimensions from bid request when missing in response', function () { + const responseNoDimensions = deepClone(serverResponse); + delete responseNoDimensions.body.seatbid[0].bid[0].w; + delete responseNoDimensions.body.seatbid[0].bid[0].h; + + const result = spec.interpretResponse(responseNoDimensions, bidRequest); + const bid = result[0]; + + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + }); + + it('should include dspPrice from ext when available', function () { + const responseWithDspPrice = deepClone(serverResponse); + responseWithDspPrice.body.seatbid[0].bid[0].ext = { dspPrice: 1.50 }; + + const result = spec.interpretResponse(responseWithDspPrice, bidRequest); + const bid = result[0]; + + expect(bid.meta.dspPrice).to.equal(1.50); + }); + + it('should return empty array for null response body', function () { + const result = spec.interpretResponse({ body: null }, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should return empty array for undefined response body', function () { + const result = spec.interpretResponse({}, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should return empty array when seatbid is not an array', function () { + const invalidResponse = { + body: { + id: '1d1a030790a475', + seatbid: 'not-an-array', + cur: 'USD' + } + }; + + const result = spec.interpretResponse(invalidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should return empty array for empty seatbid', function () { + const emptyResponse = { + body: { + id: '1d1a030790a475', + seatbid: [], + cur: 'USD' + } + }; + + const result = spec.interpretResponse(emptyResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids with zero price', function () { + const zeroPriceResponse = deepClone(serverResponse); + zeroPriceResponse.body.seatbid[0].bid[0].price = 0; + + const result = spec.interpretResponse(zeroPriceResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids with negative price', function () { + const negativePriceResponse = deepClone(serverResponse); + negativePriceResponse.body.seatbid[0].bid[0].price = -1; + + const result = spec.interpretResponse(negativePriceResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids without ad markup', function () { + const noAdmResponse = deepClone(serverResponse); + delete noAdmResponse.body.seatbid[0].bid[0].adm; + + const result = spec.interpretResponse(noAdmResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids with unknown impid', function () { + const unknownImpidResponse = deepClone(serverResponse); + unknownImpidResponse.body.seatbid[0].bid[0].impid = 'unknown-imp-id'; + + const result = spec.interpretResponse(unknownImpidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should handle missing bidRequests in request object', function () { + const result = spec.interpretResponse(serverResponse, {}); + expect(result).to.deep.equal([]); + }); + + it('should handle multiple seatbids', function () { + const multiSeatResponse = deepClone(serverResponse); + multiSeatResponse.body.seatbid.push({ + seat: 'another-dsp', + bid: [{ + id: 'another-bid-id', + impid: 'another-imp-id', + price: 2.00, + crid: 'another-creative', + adm: '
Another Ad
', + w: 728, + h: 90, + adomain: ['another-advertiser.com'] + }] + }); + + const multiBidRequest = { + bidRequests: [ + { + bidId: '30b31c1838de1e', + adUnitCode: 'adunit-code', + mediaTypes: { banner: { sizes: [[300, 250]] } } + }, + { + bidId: 'another-imp-id', + adUnitCode: 'adunit-code-2', + mediaTypes: { banner: { sizes: [[728, 90]] } } + } + ] + }; + + const result = spec.interpretResponse(multiSeatResponse, multiBidRequest); + + expect(result).to.have.lengthOf(2); + expect(result[0].meta.dsp).to.equal('test-dsp'); + expect(result[1].meta.dsp).to.equal('another-dsp'); + }); + + it('should use default currency USD when not specified', function () { + const noCurrencyResponse = deepClone(serverResponse); + delete noCurrencyResponse.body.cur; + + const result = spec.interpretResponse(noCurrencyResponse, bidRequest); + const bid = result[0]; + + expect(bid.currency).to.equal('USD'); + }); + + it('should generate creativeId when crid is missing', function () { + const noCridResponse = deepClone(serverResponse); + delete noCridResponse.body.seatbid[0].bid[0].crid; + + const result = spec.interpretResponse(noCridResponse, bidRequest); + const bid = result[0]; + + expect(bid.creativeId).to.exist; + expect(bid.creativeId).to.satisfy(crid => + crid === 'test-bid-id' || crid.startsWith('revantage-') + ); + }); + + it('should handle empty adomain array', function () { + const noAdomainResponse = deepClone(serverResponse); + delete noAdomainResponse.body.seatbid[0].bid[0].adomain; + + const result = spec.interpretResponse(noAdomainResponse, bidRequest); + const bid = result[0]; + + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); + + it('should use "unknown" for missing seat', function () { + const noSeatResponse = deepClone(serverResponse); + delete noSeatResponse.body.seatbid[0].seat; + + const result = spec.interpretResponse(noSeatResponse, bidRequest); + const bid = result[0]; + + expect(bid.meta.dsp).to.equal('unknown'); + }); + }); + + describe('getUserSyncs', function () { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + + const gdprConsent = { + gdprApplies: true, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + + const uspConsent = '1---'; + + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7, 8] + }; + + it('should return iframe sync when iframe enabled', function () { + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + gdprConsent, + uspConsent, + gppConsent + ); + + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(SYNC_URL); + }); + + it('should return pixel sync when pixel enabled', function () { + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: true }, + [], + gdprConsent, + uspConsent, + gppConsent + ); + + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.include(SYNC_URL); + expect(syncs[0].url).to.include('tag=img'); + }); + + it('should return both syncs when both enabled', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs).to.have.lengthOf(2); + expect(syncs.map(s => s.type)).to.include('iframe'); + expect(syncs.map(s => s.type)).to.include('image'); + }); + + it('should include cache buster parameter', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('cb='); + }); + + it('should include GDPR parameters when consent applies', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D'); + }); + + it('should set gdpr=0 when GDPR does not apply', function () { + const gdprNotApplies = { + gdprApplies: false, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprNotApplies, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gdpr=0'); + }); + + it('should include USP consent parameter', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('us_privacy=1---'); + }); + + it('should include GPP parameters', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gpp='); + expect(syncs[0].url).to.include('gpp_sid=7%2C8'); + }); + + it('should handle missing GDPR consent', function () { + const syncs = spec.getUserSyncs(syncOptions, [], null, uspConsent, gppConsent); + + expect(syncs[0].url).to.not.include('gdpr='); + expect(syncs[0].url).to.not.include('gdpr_consent='); + }); + + it('should handle missing USP consent', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, null, gppConsent); + + expect(syncs[0].url).to.not.include('us_privacy='); + }); + + it('should handle missing GPP consent', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, null); + + expect(syncs[0].url).to.not.include('gpp='); + expect(syncs[0].url).to.not.include('gpp_sid='); + }); + + it('should handle undefined GPP string', function () { + const partialGppConsent = { + applicableSections: [7, 8] + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, partialGppConsent); + + expect(syncs[0].url).to.not.include('gpp='); + expect(syncs[0].url).to.include('gpp_sid=7%2C8'); + }); + + it('should return empty array when no sync options enabled', function () { + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: false }, + [], + gdprConsent, + uspConsent, + gppConsent + ); + + expect(syncs).to.be.an('array').that.is.empty; + }); + + it('should return empty array when syncOptions is empty object', function () { + const syncs = spec.getUserSyncs({}, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs).to.be.an('array').that.is.empty; + }); + }); + + describe('onBidWon', function () { + let triggerPixelStub; + + beforeEach(function () { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + triggerPixelStub.restore(); + }); + + it('should call triggerPixel with correct burl', function () { + const bid = { + bidId: '30b31c1838de1e', + cpm: 1.25, + adUnitCode: 'adunit-code', + burl: 'https://bid.revantage.io/win?auction=1d1a030790a475&dsp=test-dsp&price=0.625000&impid=30b31c1838de1e&bidid=test-bid-id&adid=test-ad-123&page=https%3A%2F%2Fexample.com&domain=example.com&country=US&feedid=test-feed&ref=' + }; + + spec.onBidWon(bid); + + expect(triggerPixelStub.calledOnce).to.be.true; + expect(triggerPixelStub.firstCall.args[0]).to.include('https://bid.revantage.io/win'); + expect(triggerPixelStub.firstCall.args[0]).to.include('dsp=test-dsp'); + expect(triggerPixelStub.firstCall.args[0]).to.include('impid=30b31c1838de1e'); + expect(triggerPixelStub.firstCall.args[0]).to.include('feedid=test-feed'); + }); + + it('should not throw error when burl is missing', function () { + const bid = { + bidId: '30b31c1838de1e', + cpm: 1.25, + adUnitCode: 'adunit-code' + }; + + expect(() => spec.onBidWon(bid)).to.not.throw(); + expect(triggerPixelStub.called).to.be.false; + }); + + it('should handle burl with all query parameters', function () { + // This is the actual format generated by your RTB server + const burl = 'https://bid.revantage.io/win?' + + 'auction=auction_123456789' + + '&dsp=Improve_Digital' + + '&price=0.750000' + + '&impid=imp_001%7Cfeed123' + // URL encoded pipe for feedId in impid + '&bidid=bid_abc' + + '&adid=creative_xyz' + + '&page=https%3A%2F%2Fexample.com%2Fpage' + + '&domain=example.com' + + '&country=US' + + '&feedid=feed123' + + '&ref=https%3A%2F%2Fgoogle.com'; + + const bid = { + bidId: 'imp_001', + cpm: 1.50, + burl: burl + }; + + spec.onBidWon(bid); + + expect(triggerPixelStub.calledOnce).to.be.true; + const calledUrl = triggerPixelStub.firstCall.args[0]; + expect(calledUrl).to.include('auction=auction_123456789'); + expect(calledUrl).to.include('dsp=Improve_Digital'); + expect(calledUrl).to.include('price=0.750000'); + expect(calledUrl).to.include('domain=example.com'); + expect(calledUrl).to.include('country=US'); + expect(calledUrl).to.include('feedid=feed123'); + }); + }); + + describe('spec properties', function () { + it('should have correct bidder code', function () { + expect(spec.code).to.equal('revantage'); + }); + + it('should support banner and video media types', function () { + expect(spec.supportedMediaTypes).to.deep.equal([BANNER, VIDEO]); + }); + }); +}); diff --git a/test/spec/modules/revnewBidAdapter_spec.js b/test/spec/modules/revnewBidAdapter_spec.js new file mode 100644 index 00000000000..904b59589cb --- /dev/null +++ b/test/spec/modules/revnewBidAdapter_spec.js @@ -0,0 +1,625 @@ +import { expect } from 'chai'; +import { + spec, STORAGE, getRevnewLocalStorage, +} from 'modules/revnewBidAdapter.js'; +import sinon from 'sinon'; +import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; +const sandbox = sinon.createSandbox(); + +describe('Revnew bid adapter tests', () => { + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {}, + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page', + }, + uspConsent: '111222333', + userId: { id5id: { uid: '1111' } }, + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com', + }], + }, + }; + + describe('isBidRequestValid()', () => { + let bannerBid; + beforeEach(() => { + bannerBid = { + bidder: 'revnew', + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + adUnitCode: 'div-1', + transactionId: '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + sizes: [[300, 250], [300, 600]], + bidId: '4906582fc87d0c', + bidderRequestId: '332fda16002dbe', + auctionId: '98932591-c822-42e3-850e-4b3cf748d063', + } + }); + + it('We verify isBidRequestValid with incorrect tagid', () => { + bannerBid.params = { 'tagid': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with correct tagId', () => { + bannerBid.params = { 'tagId': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + + it('We verify isBidRequestValid with correct placement', () => { + bannerBid.params = { 'placement': 'testad' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + }); + + describe('getRevnewLocalStorage disabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => false); + }); + it('We test if we get the revnewId', () => { + const output = getRevnewLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getRevnewLocalStorage enabled but nothing', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => null); + }); + it('We test if we get the revnewId', () => { + const output = getRevnewLocalStorage(); + expect(typeof output.revnewId).to.be.eql('string'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getRevnewLocalStorage enabled but wrong payload', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => '{"revnewId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the revnewId', () => { + const output = getRevnewLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getRevnewLocalStorage enabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => '{"revnewId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the revnewId', () => { + const output = getRevnewLocalStorage(); + expect(output.revnewId).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data not set', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => null); + }); + it('We test if we get the amxId', () => { + const output = getAmxId(STORAGE, 'revnew'); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data set', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => 'abcdef'); + }); + it('We test if we get the amxId', () => { + const output = getAmxId(STORAGE, 'revnew'); + expect(output).to.be.eql('abcdef'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('buildRequests()', () => { + before(() => { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + }, + getBoundingClientRect() { return { width: 200, height: 250 }; } + }); + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => 'abcdef'); + }); + describe('We test with a multiple display bids', () => { + const sampleBids = [ + { + bidder: 'revnew', + params: { + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/revnew/Homepage/HP/Header-Ad', + }, + ortb2Imp: { + ext: { + gpid: '/12345/revnew/Homepage/HP/Header-Ad', + } + }, + adUnitCode: 'header-ad-1234', + transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', + sizes: [[300, 250], [300, 600]], + bidId: '44a2706ac3574', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + }, + { + bidder: 'revnew', + params: { + placement: 'testPlacement', + allBids: true, + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 250]] + } + }, + + adUnitCode: 'div-2-abcd', + transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', + sizes: [[728, 90], [970, 250]], + bidId: '5ba94555219a03', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + } + ]; + const bidderRequest = { + bidderCode: 'revnew', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + bidderRequestId: '359bf8a3c06b2e', + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + topmostLocation: 'https://test.nexx360.io/adapter/index.html', + location: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null, + page: 'https://test.nexx360.io/adapter/index.html', + domain: 'test.nexx360.io', + ref: null, + legacy: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + referer: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null + }, + }, + gdprConsent: { + gdprApplies: true, + consentString: 'CPhdLUAPhdLUAAKAsAENCmCsAP_AAE7AAAqIJFNd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7997BIiAaADgAJYBnwEeAJXAXmAwQBj4DtgHcgPBAeKBIgAA.YAAAAAAAAAAA', + } + }; + it('We perform a test with 2 display adunits', () => { + const displayBids = structuredClone(sampleBids); + displayBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; + const request = spec.buildRequests(displayBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].ext.revnew.tagId).to.be.eql('luvxjvgn'); + expect(requestContent.imp[0].ext.revnew.divId).to.be.eql('div-1'); + expect(requestContent.imp[1].ext.revnew.placement).to.be.eql('testPlacement'); + expect(requestContent.ext.bidderVersion).to.be.eql('1.0'); + }); + + if (FEATURES.VIDEO) { + it('We perform a test with a multiformat adunit', () => { + const multiformatBids = structuredClone(sampleBids); + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const video = request.data.imp[0].video; + const expectedVideo = { + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + w: 640, + h: 480, + ext: { + playerSize: [640, 480], + context: 'outstream', + }, + }; + expect(video).to.eql(expectedVideo); + }); + + it('We perform a test with a instream adunit', () => { + const videoBids = structuredClone(sampleBids); + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); + } + }); + after(() => { + sandbox.restore() + }); + }); + + describe('We test interpretResponse', () => { + it('empty response', () => { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); + it('banner responses with adm', () => { + const response = { + body: { + id: 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '4427551302944024629', + impid: '226175918ebeda', + price: 1.5, + adomain: [ + 'http://prebid.org', + ], + crid: '98493581', + ssp: 'appnexus', + h: 600, + w: 300, + adm: '
TestAd
', + cat: [ + 'IAB3-1', + ], + ext: { + adUnitCode: 'div-1', + mediaType: 'banner', + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + ssp: 'appnexus', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + id: 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + cookies: [], + }, + }, + }; + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '226175918ebeda', + cpm: 1.5, + width: 300, + height: 600, + creativeId: '98493581', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'http://prebid.org', + ], + demandSource: 'appnexus', + }, + ad: '
TestAd
', + }]; + expect(output).to.eql(expectedOutput); + }); + + it('instream responses', () => { + const response = { + body: { + id: '2be64380-ba0c-405a-ab53-51f51c7bde51', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '8275140264321181514', + impid: '263cba3b8bfb72', + price: 5, + adomain: [ + 'appnexus.com', + ], + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'instream', + ssp: 'appnexus', + adUnitCode: 'video1', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '263cba3b8bfb72', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + }]; + expect(output).to.eql(expectedOutput); + }); + + it('outstream responses', () => { + const response = { + body: { + id: '40c23932-135e-4602-9701-ca36f8d80c07', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '1186971142548769361', + impid: '4ce809b61a3928', + price: 5, + adomain: [ + 'appnexus.com', + ], + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'outstream', + ssp: 'appnexus', + adUnitCode: 'div-1', + divId: 'div-1', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutut = [{ + requestId: '4ce809b61a3928', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + divId: 'div-1', + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + renderer: output[0].renderer, + }]; + expect(output).to.eql(expectedOutut); + }); + + it('native responses', () => { + const response = { + body: { + id: '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '6624930625245272225', + impid: '23e11d845514bb', + price: 10, + adomain: [ + 'prebid.org', + ], + crid: '97494204', + h: 1, + w: 1, + cat: [ + 'IAB3-1', + ], + ext: { + mediaType: 'native', + ssp: 'appnexus', + adUnitCode: '/19968336/prebid_native_example_1', + }, + adm: '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}', + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectOutput = [{ + requestId: '23e11d845514bb', + cpm: 10, + width: 1, + height: 1, + creativeId: '97494204', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'native', + meta: { + advertiserDomains: [ + 'prebid.org', + ], + demandSource: 'appnexus', + }, + native: { + ortb: { + ver: '1.2', + assets: [ + { + id: 1, + img: { + url: 'https://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg', + w: 989, + h: 742, + ext: { + appnexus: { + prevent_crop: 0, + }, + }, + }, + }, + { + id: 0, + title: { + text: 'This is a Prebid Native Creative', + }, + }, + { + id: 2, + data: { + value: 'Prebid.org', + }, + }, + ], + link: { + url: 'https://ams3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA./bcr=AAAAAAAA8D8=/pp=${AUCTION_PRICE}/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA/cca=OTMyNSNBTVMzOjYxMzU=/bn=97062/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html', + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://ams3-ib.adnxs.com/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}', + }, + ], + }, + }, + }]; + expect(output).to.eql(expectOutput); + }); + }); + + describe('getUserSyncs()', () => { + const response = { body: { cookies: [] } }; + it('Verifies user sync without cookie in bid response', () => { + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with cookies in bid response', () => { + response.body.ext = { + cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + }; + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); + const expectedSyncs = [{ type: 'image', url: 'http://www.cookie.sync.org/' }]; + expect(syncs).to.eql(expectedSyncs); + }); + it('Verifies user sync with no bid response', () => { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with no bid body response', () => { + let syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + }); +}); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 5f20024fec2..fb90c793188 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -430,7 +430,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('timeout').and.to.equal(600); expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') - expect(typeof requestContent.cpuc === 'number') expect(typeof requestContent.gpid === 'string') expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) diff --git a/test/spec/modules/ringieraxelspringerBidAdapter_spec.js b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js index 08587e5174f..0318a6987c6 100644 --- a/test/spec/modules/ringieraxelspringerBidAdapter_spec.js +++ b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js @@ -1,676 +1,10 @@ import { expect } from 'chai'; 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('ringieraxelspringerBidAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - const bid = { - sizes: [[300, 250], [300, 600]], - bidder: 'ringieraxelspringer', - params: { - slot: 'slot', - area: 'areatest', - site: 'test', - network: '4178463' - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params not found', function () { - const failBid = { - sizes: [[300, 250], [300, 300]], - bidder: 'ringieraxelspringer', - params: { - site: 'test', - network: '4178463' - } - }; - expect(spec.isBidRequestValid(failBid)).to.equal(false); - }); - - it('should return nothing when bid request is malformed', function () { - const failBid = { - sizes: [[300, 250], [300, 300]], - bidder: 'ringieraxelspringer', - }; - expect(spec.isBidRequestValid(failBid)).to.equal(undefined); - }); - }); - - describe('buildRequests', function () { - const bid = { - sizes: [[300, 250], [300, 600]], - bidder: 'ringieraxelspringer', - bidId: 1, - params: { - slot: 'test', - area: 'areatest', - site: 'test', - slotSequence: '0', - network: '4178463', - customParams: { - test: 'name=value' - } - }, - mediaTypes: { - banner: { - sizes: [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - } - }; - const bid2 = { - sizes: [[750, 300]], - bidder: 'ringieraxelspringer', - bidId: 2, - params: { - slot: 'test2', - area: 'areatest', - site: 'test', - network: '4178463' - }, - mediaTypes: { - banner: { - sizes: [ - [ - 750, - 300 - ] - ] - } - } - }; - - it('should parse bids to request', function () { - const requests = spec.buildRequests([bid], { - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': 'some-consent-string' - }, - 'refererInfo': { - 'ref': 'https://example.org/', - 'page': 'https://example.com/' - } - }); - expect(requests[0].url).to.have.string(CSR_ENDPOINT); - expect(requests[0].url).to.have.string('slot0=test'); - expect(requests[0].url).to.have.string('id0=1'); - 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'); - expect(requests[0].url).to.have.string('systems=das'); - expect(requests[0].url).to.have.string('ems_url=1'); - expect(requests[0].url).to.have.string('bid_rate=1'); - expect(requests[0].url).to.have.string('gdpr_applies=true'); - expect(requests[0].url).to.have.string('euconsent=some-consent-string'); - expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); - expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); - expect(requests[0].url).to.have.string('test=name%3Dvalue'); - }); - - it('should return empty consent string when undefined', function () { - const requests = spec.buildRequests([bid]); - const gdpr = requests[0].url.search('gdpr_applies'); - const euconsent = requests[0].url.search('euconsent='); - expect(gdpr).to.equal(-1); - expect(euconsent).to.equal(-1); - }); - - it('should parse bids to request from pageContext', function () { - const bidCopy = { ...bid }; - bidCopy.params = { - ...bid.params, - pageContext: { - dv: 'test/areatest', - du: 'https://example.com/', - dr: 'https://example.org/', - keyWords: ['val1', 'val2'], - keyValues: { - adunit: 'test/areatest' - } - } - }; - const requests = spec.buildRequests([bidCopy, bid2]); - - expect(requests[0].url).to.have.string(CSR_ENDPOINT); - expect(requests[0].url).to.have.string('slot0=test'); - 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'); - expect(requests[0].url).to.have.string('systems=das'); - expect(requests[0].url).to.have.string('ems_url=1'); - expect(requests[0].url).to.have.string('bid_rate=1'); - expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); - expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); - expect(requests[0].url).to.have.string('DV=test%2Fareatest'); - expect(requests[0].url).to.have.string('kwrd=val1%2Bval2'); - expect(requests[0].url).to.have.string('kvadunit=test%2Fareatest'); - expect(requests[0].url).to.have.string('pos0=0'); - }); - - it('should parse dsainfo when available', function () { - const bidCopy = { ...bid }; - bidCopy.params = { - ...bid.params, - pageContext: { - dv: 'test/areatest', - du: 'https://example.com/', - dr: 'https://example.org/', - keyWords: ['val1', 'val2'], - keyValues: { - adunit: 'test/areatest' - } - } - }; - const bidderRequest = { - ortb2: { - regs: { - ext: { - dsa: { - required: 1 - } - } - } - } - }; - let requests = spec.buildRequests([bidCopy], bidderRequest); - expect(requests[0].url).to.have.string('dsainfo=1'); - - bidderRequest.ortb2.regs.ext.dsa.required = 0; - requests = spec.buildRequests([bidCopy], bidderRequest); - expect(requests[0].url).to.have.string('dsainfo=0'); - }); - }); - - describe('interpretResponse', function () { - const response = { - 'adsCheck': 'ok', - 'geoloc': {}, - 'ir': '92effd60-0c84-4dac-817e-763ea7b8ac65', - 'ads': [ - { - 'id': 'flat-belkagorna', - 'slot': 'flat-belkagorna', - 'prio': 10, - 'type': 'html', - 'bid_rate': 0.321123, - 'adid': 'das,50463,152276', - 'id_3': '12734', - 'html': '' - } - ], - 'iv': '202003191334467636346500' - }; - - it('should get correct bid response', function () { - const resp = spec.interpretResponse({ body: response }, { bidIds: [{ slot: 'flat-belkagorna', bidId: 1 }] }); - expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'ad', 'meta', 'actgMatch', 'mediaType'); - expect(resp.length).to.equal(1); - }); - - it('should handle empty ad', function () { - const res = { - 'ads': [{ - type: 'empty' - }] - }; - const resp = spec.interpretResponse({ body: res }, {}); - expect(resp).to.deep.equal([]); - }); - - it('should handle empty server response', function () { - const res = { - 'ads': [] - }; - const resp = spec.interpretResponse({ body: res }, {}); - expect(resp).to.deep.equal([]); - }); - - it('should generate auctionConfig when fledge is enabled', function () { - const bidRequest = { - method: 'GET', - url: 'https://example.com', - bidIds: [{ - slot: 'top', - bidId: '123', - network: 'testnetwork', - sizes: ['300x250'], - params: { - site: 'testsite', - area: 'testarea', - network: 'testnetwork' - }, - fledgeEnabled: true - }, - { - slot: 'top', - bidId: '456', - network: 'testnetwork', - sizes: ['300x250'], - params: { - site: 'testsite', - area: 'testarea', - network: 'testnetwork' - }, - fledgeEnabled: false - }] - }; - - const auctionConfigs = [{ - 'bidId': '123', - 'config': { - 'seller': 'https://csr.onet.pl', - 'decisionLogicUrl': 'https://csr.onet.pl/testnetwork/v1/protected-audience-api/decision-logic.js', - 'interestGroupBuyers': ['https://csr.onet.pl'], - 'auctionSignals': { - 'params': { - site: 'testsite', - area: 'testarea', - network: 'testnetwork' - }, - 'sizes': ['300x250'], - 'gctx': '1234567890' - } - } - }]; - const resp = spec.interpretResponse({body: {gctx: '1234567890'}}, bidRequest); - expect(resp).to.deep.equal({bids: [], paapi: auctionConfigs}); - }); - }); - - describe('buildNativeRequests', function () { - const bid = { - sizes: 'fluid', - bidder: 'ringieraxelspringer', - bidId: 1, - params: { - slot: 'nativestd', - area: 'areatest', - site: 'test', - slotSequence: '0', - network: '4178463', - customParams: { - test: 'name=value' - } - }, - mediaTypes: { - native: { - clickUrl: { - required: true - }, - image: { - required: true - }, - sponsoredBy: { - len: 25, - required: true - }, - title: { - len: 50, - required: true - } - } - } - }; - - it('should parse bids to native request', function () { - const requests = spec.buildRequests([bid], { - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': 'some-consent-string' - }, - 'refererInfo': { - 'ref': 'https://example.org/', - 'page': 'https://example.com/' - } - }); - - expect(requests[0].url).to.have.string(CSR_ENDPOINT); - expect(requests[0].url).to.have.string('slot0=nativestd'); - expect(requests[0].url).to.have.string('id0=1'); - 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'); - expect(requests[0].url).to.have.string('systems=das'); - expect(requests[0].url).to.have.string('ems_url=1'); - expect(requests[0].url).to.have.string('bid_rate=1'); - expect(requests[0].url).to.have.string('gdpr_applies=true'); - expect(requests[0].url).to.have.string('euconsent=some-consent-string'); - expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); - 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'); - }); - }); - - describe('interpretNativeResponse', function () { - const response = { - 'adsCheck': 'ok', - 'geoloc': {}, - 'ir': '92effd60-0c84-4dac-817e-763ea7b8ac65', - 'iv': '202003191334467636346500', - 'ads': [ - { - 'id': 'nativestd', - 'slot': 'nativestd', - 'prio': 10, - 'type': 'native', - 'bid_rate': 0.321123, - 'adid': 'das,50463,152276', - 'id_3': '12734' - } - ] - }; - const responseTeaserStandard = { - adsCheck: 'ok', - geoloc: {}, - ir: '92effd60-0c84-4dac-817e-763ea7b8ac65', - iv: '202003191334467636346500', - ads: [ - { - id: 'nativestd', - slot: 'nativestd', - prio: 10, - type: 'native', - bid_rate: 0.321123, - adid: 'das,50463,152276', - id_3: '12734', - data: { - fields: { - leadtext: 'BODY', - title: 'Headline', - image: '//img.url', - url: '//link.url', - partner_logo: '//logo.url', - adInfo: 'REKLAMA', - impression: '//impression.url', - impression1: '//impression1.url', - impressionJs1: '//impressionJs1.url' - }, - meta: { - slot: 'nativestd', - height: 1, - width: 1, - advertiser_name: 'Test Onet', - dsaurl: '//dsa.url', - adclick: '//adclick.url' - } - }, - ems_link: '//ems.url' - } - ] - }; - const responseNativeInFeed = { - adsCheck: 'ok', - geoloc: {}, - ir: '92effd60-0c84-4dac-817e-763ea7b8ac65', - iv: '202003191334467636346500', - ads: [ - { - id: 'nativestd', - slot: 'nativestd', - prio: 10, - type: 'native', - bid_rate: 0.321123, - adid: 'das,50463,152276', - id_3: '12734', - data: { - fields: { - Body: 'BODY', - Calltoaction: 'Calltoaction', - Headline: 'Headline', - Image: '//img.url', - adInfo: 'REKLAMA', - Thirdpartyclicktracker: '//link.url', - imp: '//imp.url', - thirdPartyClickTracker2: '//thirdPartyClickTracker.url' - }, - meta: { - slot: 'nativestd', - height: 1, - width: 1, - advertiser_name: 'Test Onet', - dsaurl: '//dsa.url', - adclick: '//adclick.url' - } - }, - ems_link: '//ems.url' - } - ] - }; - const expectedTeaserStandardOrtbResponse = { - ver: '1.2', - assets: [ - { - id: 0, - data: { - value: '', - type: 2 - }, - }, - { - id: 1, - data: { - value: 'REKLAMA', - type: 10 - }, - }, - { - id: 3, - img: { - type: 1, - url: '//logo.url', - w: 1, - h: 1 - } - }, - { - id: 4, - img: { - type: 3, - url: '//img.url', - w: 1, - h: 1 - } - }, - { - id: 5, - data: { - value: 'Test Onet', - type: 1 - }, - }, - { - id: 6, - title: { - text: 'Headline' - } - }, - ], - link: { - url: '//adclick.url//link.url', - clicktrackers: [] - }, - eventtrackers: [ - { - event: 1, - method: 1, - url: '//ems.url' - }, - { - event: 1, - method: 1, - url: '//impression.url' - }, - { - event: 1, - method: 1, - url: '//impression1.url' - }, - { - event: 1, - method: 2, - url: '//impressionJs1.url' - } - ], - privacy: '//dsa.url' - }; - const expectedTeaserStandardResponse = { - title: 'Headline', - image: { - url: '//img.url', - 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' - }; - const expectedNativeInFeedOrtbResponse = { - ver: '1.2', - assets: [ - { - id: 0, - data: { - value: '', - type: 2 - }, - }, - { - id: 1, - data: { - value: 'REKLAMA', - type: 10 - }, - }, - { - id: 3, - img: { - type: 1, - url: '', - w: 1, - h: 1 - } - }, - { - id: 4, - img: { - type: 3, - url: '//img.url', - w: 1, - h: 1 - } - }, - { - id: 5, - data: { - value: 'Test Onet', - type: 1 - }, - }, - { - id: 6, - title: { - text: 'Headline' - } - }, - ], - link: { - url: '//adclick.url//link.url', - clicktrackers: ['//thirdPartyClickTracker.url'] - }, - eventtrackers: [ - { - event: 1, - method: 1, - url: '//ems.url' - }, - { - event: 1, - method: 1, - url: '//imp.url' - } - ], - privacy: '//dsa.url', - }; - const expectedNativeInFeedResponse = { - title: 'Headline', - image: { - url: '//img.url', - 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' - }; - - it('should get correct bid native response', function () { - const resp = spec.interpretResponse({ body: response }, { bidIds: [{ slot: 'nativestd', bidId: 1, mediaType: 'native' }] }); - - expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'meta', 'actgMatch', 'mediaType', 'native'); - expect(resp.length).to.equal(1); - }); - - it('should get correct native response for TeaserStandard', function () { - const resp = spec.interpretResponse({ body: responseTeaserStandard }, { bidIds: [{ slot: 'nativestd', bidId: 1, mediaType: 'native' }] }); - const teaserStandardResponse = resp[0].native; - - expect(JSON.stringify(teaserStandardResponse)).to.equal(JSON.stringify(expectedTeaserStandardResponse)); - }); - - it('should get correct native response for NativeInFeed', function () { - const resp = spec.interpretResponse({ body: responseNativeInFeed }, { bidIds: [{ slot: 'nativestd', bidId: 1, mediaType: 'native' }] }); - const nativeInFeedResponse = resp[0].native; - - expect(JSON.stringify(nativeInFeedResponse)).to.equal(JSON.stringify(expectedNativeInFeedResponse)); - }); +describe('ringieraxelspringer backward-compatibility shim', function () { + it('should re-export spec from dasBidAdapter', function () { + expect(spec).to.exist; + expect(spec.code).to.equal('das'); + expect(spec.aliases).to.include('ringieraxelspringer'); }); }); diff --git a/test/spec/modules/rocketlabBidAdapter_spec.js b/test/spec/modules/rocketlabBidAdapter_spec.js index fc162c67959..ffe48e4c2d9 100644 --- a/test/spec/modules/rocketlabBidAdapter_spec.js +++ b/test/spec/modules/rocketlabBidAdapter_spec.js @@ -544,7 +544,7 @@ describe("RocketLabBidAdapter", function () { consentString: "ALL", gdprApplies: true, }, - {} + undefined ); expect(syncData).to.be.an("array").which.is.not.empty; expect(syncData[0]).to.be.an("object"); @@ -560,9 +560,7 @@ describe("RocketLabBidAdapter", function () { {}, {}, {}, - { - consentString: "1---", - } + "1---" ); expect(syncData).to.be.an("array").which.is.not.empty; expect(syncData[0]).to.be.an("object"); @@ -578,7 +576,7 @@ describe("RocketLabBidAdapter", function () { {}, {}, {}, - {}, + undefined, { gppString: "abc123", applicableSections: [8], diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b96a5e4fd4f..831f349cef5 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1815,7 +1815,7 @@ describe('the rubicon adapter', function () { }) }) - it('should send gpid and pbadslot since it is prefered over dfp code', function () { + it('should send gpid and pbadslot since it is preferred over dfp code', function () { bidderRequest.bids[0].ortb2Imp = { ext: { gpid: '/1233/sports&div1', @@ -2890,88 +2890,6 @@ describe('the rubicon adapter', function () { expect(slotParams.kw).to.equal('a,b,c'); }); - it('should pass along o_ae param when fledge is enabled', () => { - const localBidRequest = Object.assign({}, bidderRequest.bids[0]); - localBidRequest.ortb2Imp.ext.ae = true; - - const slotParams = spec.createSlotParams(localBidRequest, bidderRequest); - - expect(slotParams['o_ae']).to.equal(1) - }); - - it('should pass along desired segtaxes, but not non-desired ones', () => { - const localBidderRequest = Object.assign({}, bidderRequest); - localBidderRequest.refererInfo = {domain: 'bob'}; - config.setConfig({ - rubicon: { - sendUserSegtax: [9], - sendSiteSegtax: [10] - } - }); - localBidderRequest.ortb2.user = { - data: [{ - ext: { - segtax: '404' - }, - segment: [{id: 5}, {id: 6}] - }, { - ext: { - segtax: '508' - }, - segment: [{id: 5}, {id: 2}] - }, { - ext: { - segtax: '9' - }, - segment: [{id: 1}, {id: 2}] - }] - } - localBidderRequest.ortb2.site = { - content: { - data: [{ - ext: { - segtax: '10' - }, - segment: [{id: 2}, {id: 3}] - }, { - ext: { - segtax: '507' - }, - segment: [{id: 3}, {id: 4}] - }] - } - } - const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); - expect(slotParams['tg_i.tax507']).is.equal('3,4'); - expect(slotParams['tg_v.tax508']).is.equal('5,2'); - expect(slotParams['tg_v.tax9']).is.equal('1,2'); - expect(slotParams['tg_i.tax10']).is.equal('2,3'); - expect(slotParams['tg_v.tax404']).is.equal(undefined); - }); - - it('should support IAB segtax 7 in site segments', () => { - const localBidderRequest = Object.assign({}, bidderRequest); - localBidderRequest.refererInfo = {domain: 'bob'}; - config.setConfig({ - rubicon: { - sendUserSegtax: [4], - sendSiteSegtax: [1, 2, 5, 6, 7] - } - }); - localBidderRequest.ortb2.site = { - content: { - data: [{ - ext: { - segtax: '7' - }, - segment: [{id: 8}, {id: 9}] - }] - } - }; - const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); - expect(slotParams['tg_i.tax7']).to.equal('8,9'); - }); - it('should add p_site.mobile if mobile is a number in ortb2.site', function () { // Set up a bidRequest with mobile property as a number const localBidderRequest = Object.assign({}, bidderRequest); @@ -3816,43 +3734,6 @@ describe('the rubicon adapter', function () { expect(bids).to.be.lengthOf(0); }); - it('Should support recieving an auctionConfig and pass it along to Prebid', function () { - const response = { - 'status': 'ok', - 'account_id': 14062, - 'site_id': 70608, - 'zone_id': 530022, - 'size_id': 15, - 'alt_size_ids': [ - 43 - ], - 'tracking': '', - 'inventory': {}, - 'ads': [{ - 'status': 'ok', - 'cpm': 0, - 'size_id': 15 - }], - 'component_auction_config': [{ - 'random': 'value', - 'bidId': '5432' - }, - { - 'random': 'string', - 'bidId': '6789' - }] - }; - - const {bids, paapi} = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); - - expect(bids).to.be.lengthOf(1); - 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 () { const response = { 'status': 'ok', @@ -3979,6 +3860,98 @@ describe('the rubicon adapter', function () { expect(bids[1].meta.mediaType).to.equal('video'); }); + it('should handle primaryCatId and secondaryCatIds when bid.bid_cat is present in response', function () { + const response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'emulated_format': 'video', + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ], + 'bid_cat': ['IAB1-1'] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ], + 'bid_cat': ['IAB1-2', 'IAB1-3'] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 10, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ] + }; + const bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids[0].meta.primaryCatId).to.be.undefined; + expect(bids[0].meta.secondaryCatIds).to.be.undefined; + expect(bids[1].meta.primaryCatId).to.equal(response.ads[1].bid_cat[0]); + expect(bids[1].meta.secondaryCatIds).to.deep.equal(response.ads[1].bid_cat.slice(1)); + expect(bids[2].meta.primaryCatId).to.equal(response.ads[0].bid_cat[0]); + expect(bids[2].meta.secondaryCatIds).to.be.undefined; + }); + describe('singleRequest enabled', function () { it('handles bidRequest of type Array and returns associated adUnits', function () { const overrideMap = []; diff --git a/test/spec/modules/rules_spec.js b/test/spec/modules/rules_spec.js new file mode 100644 index 00000000000..12353de1a2e --- /dev/null +++ b/test/spec/modules/rules_spec.js @@ -0,0 +1,856 @@ +import { expect } from 'chai'; +import * as rulesModule from 'modules/rules/index.ts'; +import * as utils from 'src/utils.js'; +import * as storageManager from 'src/storageManager.js'; +import * as analyticsAdapter from 'libraries/analyticsAdapter/AnalyticsAdapter.ts'; +import { isActivityAllowed } from 'src/activities/rules.js'; +import { activityParams } from 'src/activities/activityParams.js'; +import { ACTIVITY_FETCH_BIDS, ACTIVITY_ADD_BID_RESPONSE } from 'src/activities/activities.js'; +import { MODULE_TYPE_BIDDER } from 'src/activities/modules.ts'; +import { config } from 'src/config.js'; + +describe('Rules Module', function() { + let sandbox; + let logWarnStub; + let logInfoStub; + let newStorageManagerStub; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + + logWarnStub = sandbox.stub(utils, 'logWarn'); + logInfoStub = sandbox.stub(utils, 'logInfo'); + + const mockStorageManager = { + localStorageIsEnabled: sandbox.stub().returns(true), + getDataFromLocalStorage: sandbox.stub().returns(null), + setDataInLocalStorage: sandbox.stub() + }; + newStorageManagerStub = sandbox.stub(storageManager, 'newStorageManager').returns(mockStorageManager); + }); + + afterEach(function() { + sandbox.restore(); + config.resetConfig(); + rulesModule.reset(); + }); + + describe('getAssignedModelGroups', function() { + it('should select model group based on weights', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 50, + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 50, + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }] + }]; + + // Mock Math.random to return 0.15 (15 < 50, so first group should be selected) + // randomValue = 0.15 * 100 = 15, 15 < 50 so first group selected + sandbox.stub(Math, 'random').returns(0.15); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + // Verify that first model group was selected in the returned result + expect(result[0].modelGroups[0].selected).to.be.true; + expect(result[0].modelGroups[1].selected).to.be.false; + // Verify original was not mutated + expect(rulesets[0].modelGroups[0].selected).to.be.false; + expect(rulesets[0].modelGroups[1].selected).to.be.false; + }); + + it('should use default weight of 100 when weight is not specified', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + // weight not specified, should default to 100 + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + // weight not specified, should default to 100 + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + // weight not specified, should default to 100 + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // Mock Math.random to return value that selects last group + // randomValue = 0.85 * 300 = 255 + // Cumulative weights: [100, 200, 300] + // First group: 255 < 100? No + // Second group: 255 < 200? No + // Third group: 255 < 300? Yes, selected! + sandbox.stub(Math, 'random').returns(0.85); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.false; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.true; + // Verify original was not mutated + expect(rulesets[0].modelGroups[0].selected).to.be.false; + expect(rulesets[0].modelGroups[1].selected).to.be.false; + expect(rulesets[0].modelGroups[2].selected).to.be.false; + }); + + it('should select correctly regardless of weight order (descending)', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 100, // largest first + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 50, // medium + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 25, // smallest last + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.3 * 175 = 52.5 + // Cumulative weights: [100, 150, 175] + // First group: 52.5 < 100? Yes, selected! + sandbox.stub(Math, 'random').returns(0.3); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.true; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.false; + }); + + it('should select correctly regardless of weight order (mixed)', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 30, // medium + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 100, // largest in middle + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 20, // smallest last + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.6 * 150 = 90 + // Cumulative weights: [30, 130, 150] + // First group: 90 < 30? No + // Second group: 90 < 130? Yes, selected! + sandbox.stub(Math, 'random').returns(0.6); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.false; + expect(result[0].modelGroups[1].selected).to.be.true; + expect(result[0].modelGroups[2].selected).to.be.false; + }); + + it('should select last group when randomValue is in last range', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 10, + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 20, + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 70, // largest weight + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.8 * 100 = 80 + // Cumulative weights: [10, 30, 100] + // First group: 80 < 10? No + // Second group: 80 < 30? No + // Third group: 80 < 100? Yes, selected! + sandbox.stub(Math, 'random').returns(0.8); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.false; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.true; + }); + + it('should select first group when randomValue is very small', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 10, // smallest + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 50, + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 40, + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.01 * 100 = 1 + // Cumulative weights: [10, 60, 100] + // First group: 1 < 10? Yes, selected! + sandbox.stub(Math, 'random').returns(0.01); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.true; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.false; + }); + }); + + describe('evaluateConfig', function() { + beforeEach(function() { + rulesModule.registerActivities(); + }); + + [ + ['processed-auction-request', ACTIVITY_FETCH_BIDS], + ['processed-auction', ACTIVITY_ADD_BID_RESPONSE] + ].forEach(([stage, activity]) => { + it(`should exclude bidder when it matches bidders list for ${stage} stage`, function() { + const rulesJson = { + enabled: true, + timestamp: '1234567890', + ruleSets: [{ + name: 'testRuleSet', + stage: stage, + version: '1.0', + modelGroups: [{ + weight: 100, + selected: true, + analyticsKey: 'testAnalyticsKey', + schema: [{ function: 'adUnitCode', args: [] }], + rules: [{ + conditions: ['adUnit-0000'], + results: [{ + function: 'excludeBidders', + args: [{ + bidders: ['bidder1'], + analyticsValue: 'excluded' + }] + }] + }] + }] + }] + }; + + sandbox.stub(Math, 'random').returns(0.5); + + const bidder1Params = activityParams(MODULE_TYPE_BIDDER, 'bidder1', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + const bidder2Params = activityParams(MODULE_TYPE_BIDDER, 'bidder2', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.true; + expect(isActivityAllowed(activity, bidder2Params)).to.be.true; + + rulesModule.evaluateConfig(rulesJson, 'test-auction-id'); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.false; + expect(isActivityAllowed(activity, bidder2Params)).to.be.true; + }); + + it(`should include only bidder when it matches bidders list for ${stage} stage`, function() { + const rulesJson = { + enabled: true, + timestamp: '1234567890', + ruleSets: [{ + name: 'testRuleSet', + stage: stage, + version: '1.0', + modelGroups: [{ + weight: 100, + selected: true, + analyticsKey: 'testAnalyticsKey', + schema: [{ function: 'adUnitCode', args: [] }], + rules: [{ + conditions: ['adUnit-0000'], + results: [{ + function: 'includeBidders', + args: [{ + bidders: ['bidder1'], + analyticsValue: 'included' + }] + }] + }] + }] + }] + }; + + sandbox.stub(Math, 'random').returns(0.5); + + const bidder1Params = activityParams(MODULE_TYPE_BIDDER, 'bidder1', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + const bidder2Params = activityParams(MODULE_TYPE_BIDDER, 'bidder2', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.true; + expect(isActivityAllowed(activity, bidder2Params)).to.be.true; + + rulesModule.evaluateConfig(rulesJson, 'test-auction-id'); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.true; + expect(isActivityAllowed(activity, bidder2Params)).to.be.false; + }); + }); + + it('should execute default rules when provided and no rules match', function() { + const setLabelsStub = sandbox.stub(analyticsAdapter, 'setLabels'); + const rulesJson = { + enabled: true, + timestamp: '1234567890', + ruleSets: [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + version: '1.0', + modelGroups: [{ + weight: 100, + selected: true, + analyticsKey: 'testAnalyticsKey', + schema: [{ + function: 'percent', + args: [5] + }], + default: [{ + function: 'logAtag', + args: { analyticsValue: 'default-allow' } + }], + rules: [{ + conditions: ['true'], + results: [{ + function: 'excludeBidders', + args: [{ + bidders: ['bidder1'], + analyticsValue: 'excluded' + }] + }] + }] + }] + }] + }; + + sandbox.stub(Math, 'random').returns(0.5); + const auctionId = 'test-auction-id'; + rulesModule.evaluateConfig(rulesJson, auctionId); + + const bidder1Params = activityParams(MODULE_TYPE_BIDDER, 'bidder1', { + auctionId + }); + + expect(isActivityAllowed(ACTIVITY_FETCH_BIDS, bidder1Params)).to.be.true; + + expect(setLabelsStub.calledWith({ [auctionId + '-testAnalyticsKey']: 'default-allow' })).to.be.true; + + setLabelsStub.resetHistory(); + }); + }); + + describe('getGlobalRandom', function() { + it('should return the same value for the same auctionId and call Math.random only once', function() { + const auctionId = 'test-auction-id'; + const otherAuctionId = 'other-auction-id'; + const mathRandomStub = sandbox.stub(Math, 'random').returns(0.42); + const auction1 = {auctionId: auctionId}; + const auction2 = {auctionId: otherAuctionId}; + const auctions = { + [auctionId]: auction1, + [otherAuctionId]: auction2 + } + + const index = { + getAuction: ({auctionId}) => auctions[auctionId] + } + + const result1 = rulesModule.dep.getGlobalRandom(auctionId, index); + const result2 = rulesModule.dep.getGlobalRandom(auctionId, index); + const result3 = rulesModule.dep.getGlobalRandom(auctionId, index); + + expect(result1).to.equal(0.42); + expect(result2).to.equal(0.42); + expect(result3).to.equal(0.42); + expect(mathRandomStub.calledOnce).to.equal(true); + + mathRandomStub.returns(0.99); + const result4 = rulesModule.dep.getGlobalRandom(otherAuctionId, index); + + expect(result4).to.equal(0.99); + expect(mathRandomStub.calledTwice).to.equal(true); + }); + }); + + describe('evaluateSchema', function() { + it('should evaluate percent condition', function() { + sandbox.stub(rulesModule.dep, 'getGlobalRandom').returns(0.3); + const func = rulesModule.evaluateSchema('percent', [50], {}); + const result = func(); + // 30 < 50, so should return true + expect(result).to.be.true; + }); + + it('should evaluate adUnitCode condition', function() { + const context = { + adUnit: { + code: 'div-1' + } + }; + const func = rulesModule.evaluateSchema('adUnitCode', [], context); + expect(func()).to.equal('div-1'); + + const func2 = rulesModule.evaluateSchema('adUnitCode', [], context); + expect(func2()).to.equal('div-1'); + }); + + it('should evaluate adUnitCodeIn condition', function() { + const context = { + adUnit: { + code: 'div-1' + } + }; + const func = rulesModule.evaluateSchema('adUnitCodeIn', [['div-1', 'div-2']], context); + expect(func()).to.be.true; + + const func2 = rulesModule.evaluateSchema('adUnitCodeIn', [['div-3', 'div-4']], context); + expect(func2()).to.be.false; + }); + + it('should evaluate deviceCountry condition', function() { + const context = { + ortb2: { + device: { + geo: { + country: 'US' + } + } + } + }; + const func = rulesModule.evaluateSchema('deviceCountry', [], context); + expect(func()).to.equal('US'); + + const func2 = rulesModule.evaluateSchema('deviceCountry', [], context); + expect(func2()).to.equal('US'); + }); + + it('should evaluate deviceCountryIn condition', function() { + const context = { + ortb2: { + device: { + geo: { + country: 'US' + } + } + } + }; + const func = rulesModule.evaluateSchema('deviceCountryIn', [['US', 'UK']], context); + expect(func()).to.be.true; + + const func2 = rulesModule.evaluateSchema('deviceCountryIn', [['DE', 'FR']], context); + expect(func2()).to.be.false; + }); + + it('should evaluate channel condition', function() { + const context1 = { + ortb2: { + ext: { + prebid: { + channel: 'pbjs' + } + } + } + }; + const func1 = rulesModule.evaluateSchema('channel', [], context1); + expect(func1()).to.equal('web'); + }); + + it('should evaluate eidAvailable condition', function() { + const context1 = { + ortb2: { + user: { + eids: [{ source: 'test', id: '123' }] + } + } + }; + const func1 = rulesModule.evaluateSchema('eidAvailable', [], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + user: { + eids: [] + } + } + }; + const func2 = rulesModule.evaluateSchema('eidAvailable', [], context2); + expect(func2()).to.be.false; + }); + + it('should evaluate userFpdAvailable condition', function() { + const context1 = { + ortb2: { + user: { + data: [{ name: 'test', segment: [] }] + } + } + }; + const func1 = rulesModule.evaluateSchema('userFpdAvailable', [], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + user: { + ext: { + data: [{ name: 'test', segment: [] }] + } + } + } + }; + const func2 = rulesModule.evaluateSchema('userFpdAvailable', [], context2); + expect(func2()).to.be.true; + + const context3 = { + ortb2: { + user: {} + } + }; + const func3 = rulesModule.evaluateSchema('userFpdAvailable', [], context3); + expect(func3()).to.be.false; + }); + + it('should evaluate fpdAvailable condition', function() { + const context1 = { + ortb2: { + user: { + data: [{ name: 'test' }] + } + } + }; + const func1 = rulesModule.evaluateSchema('fpdAvailable', [], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + site: { + content: { + data: [{ name: 'test' }] + } + } + } + }; + const func2 = rulesModule.evaluateSchema('fpdAvailable', [], context2); + expect(func2()).to.be.true; + + const context3 = { + ortb2: {} + }; + const func3 = rulesModule.evaluateSchema('fpdAvailable', [], context3); + expect(func3()).to.be.false; + }); + + it('should evaluate gppSidIn condition', function() { + const context1 = { + ortb2: { + regs: { + gpp_sid: [1, 2, 3] + } + } + }; + const func1 = rulesModule.evaluateSchema('gppSidIn', [[2]], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('gppSidIn', [[4]], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate tcfInScope condition', function() { + const context1 = { + ortb2: { + regs: { + ext: { + gdpr: 1 + } + } + } + }; + const func1 = rulesModule.evaluateSchema('tcfInScope', [], context1); + expect(func1()).to.be.true; + + const context2 = { + regs: { + ext: { + gdpr: 0 + } + } + }; + const func2 = rulesModule.evaluateSchema('tcfInScope', [], context2); + expect(func2()).to.be.false; + }); + + it('should evaluate domain condition', function() { + const context1 = { + ortb2: { + site: { + domain: 'example.com' + } + } + }; + const func1 = rulesModule.evaluateSchema('domain', [], context1); + expect(func1()).to.equal('example.com'); + + const context2 = { + ortb2: { + app: { + domain: 'app.example.com' + } + } + }; + const func2 = rulesModule.evaluateSchema('domain', [], context2); + expect(func2()).to.equal('app.example.com'); + + const context3 = { + ortb2: {} + }; + const func3 = rulesModule.evaluateSchema('domain', [], context3); + expect(func3()).to.equal(''); + }); + + it('should evaluate domainIn condition', function() { + const context1 = { + ortb2: { + site: { + domain: 'example.com' + } + } + }; + const func1 = rulesModule.evaluateSchema('domainIn', [['example.com', 'test.com']], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + app: { + domain: 'app.example.com' + } + } + }; + const func2 = rulesModule.evaluateSchema('domainIn', [['app.example.com']], context2); + expect(func2()).to.be.true; + + const func3 = rulesModule.evaluateSchema('domainIn', [['other.com']], context1); + expect(func3()).to.be.false; + }); + + it('should evaluate bundle condition', function() { + const context1 = { + ortb2: { + app: { + bundle: 'com.example.app' + } + } + }; + const func1 = rulesModule.evaluateSchema('bundle', [], context1); + expect(func1()).to.equal('com.example.app'); + + const context2 = { + ortb2: {} + }; + const func2 = rulesModule.evaluateSchema('bundle', [], context2); + expect(func2()).to.equal(''); + }); + + it('should evaluate bundleIn condition', function() { + const context1 = { + ortb2: { + app: { + bundle: 'com.example.app' + } + } + }; + const func1 = rulesModule.evaluateSchema('bundleIn', ['com.example.app'], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('bundleIn', [['com.other.app']], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate mediaTypeIn condition', function() { + const context1 = { + adUnit: { + mediaTypes: { + banner: {}, + video: {} + } + } + }; + const func1 = rulesModule.evaluateSchema('mediaTypeIn', [['banner']], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('mediaTypeIn', [['native']], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate deviceTypeIn condition', function() { + const context1 = { + ortb2: { + device: { + devicetype: 2 + } + } + }; + const func1 = rulesModule.evaluateSchema('deviceTypeIn', [[2, 3]], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('deviceTypeIn', [[4, 5]], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate bidPrice condition', function() { + const context1 = { + bid: { + cpm: 5.50, + currency: 'USD' + } + }; + const func1 = rulesModule.evaluateSchema('bidPrice', ['gt', 'USD', 5.0], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('bidPrice', ['gt', 'USD', 6.0], context1); + expect(func2()).to.be.false; + + const func3 = rulesModule.evaluateSchema('bidPrice', ['lte', 'USD', 6.0], context1); + expect(func3()).to.be.true; + + const context3 = { + bid: { + cpm: 0, + currency: 'USD' + } + }; + const func4 = rulesModule.evaluateSchema('bidPrice', ['gt', 'USD', 1.0], context3); + expect(func4()).to.be.false; + }); + + it('should return null function for unknown schema function', function() { + const func = rulesModule.evaluateSchema('unknownFunction', [], {}); + expect(func()).to.be.null; + }); + + describe('extraSchemaEvaluators', function() { + it('should use custom browser evaluator from extraSchemaEvaluators', function() { + const browserEvaluator = (args, context) => { + return () => { + const userAgent = context.ortb2?.device?.ua || navigator.userAgent; + if (userAgent.includes('Chrome')) return 'Chrome'; + if (userAgent.includes('Firefox')) return 'Firefox'; + if (userAgent.includes('Safari')) return 'Safari'; + if (userAgent.includes('Edge')) return 'Edge'; + return 'Unknown'; + }; + }; + + config.setConfig({ + shapingRules: { + extraSchemaEvaluators: { + browser: browserEvaluator + } + } + }); + + const context1 = { + ortb2: { + device: { + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0' + } + } + }; + + const func1 = rulesModule.evaluateSchema('browser', [], context1); + expect(func1()).to.equal('Chrome'); + + const context2 = { + ortb2: { + device: { + ua: 'Mozilla/5.0 Firefox/121.0.0' + } + } + }; + const func2 = rulesModule.evaluateSchema('browser', [], context2); + expect(func2()).to.equal('Firefox'); + }); + }); + }); +}); diff --git a/test/spec/modules/screencoreBidAdapter_spec.js b/test/spec/modules/screencoreBidAdapter_spec.js index 4e9177e8ce5..bd1aad95edf 100644 --- a/test/spec/modules/screencoreBidAdapter_spec.js +++ b/test/spec/modules/screencoreBidAdapter_spec.js @@ -1,789 +1,396 @@ import { expect } from 'chai'; -import { createDomain, spec as adapter, storage } from 'modules/screencoreBidAdapter.js'; -import { getGlobal } from 'src/prebidGlobal.js'; -import { - extractCID, - extractPID, - extractSubDomain, - getStorageItem, - getUniqueDealId, - hashCode, - setStorageItem, - tryParseJSON, -} from 'libraries/vidazooUtils/bidderUtils.js'; +import { createDomain, spec as adapter } from 'modules/screencoreBidAdapter.js'; import { config } from 'src/config.js'; import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; -import { version } from 'package.json'; -import * as utils from 'src/utils.js'; -import sinon, { useFakeTimers } from 'sinon'; - -export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'netId', 'tdid', 'pubProvidedId', 'intentIqId', 'liveIntentId']; - -const SUB_DOMAIN = 'exchange'; +import sinon from 'sinon'; 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' - }, - 'placementId': 'testBanner' + bidId: '2d52001cabd527', + bidder: 'screencore', + adUnitCode: 'div-gpt-ad-12345-0', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + params: { + placementId: 'testPlacement', + endpointId: 'testEndpoint' }, - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [[300, 250], [300, 600]], - 'bidderRequestId': '1fdb5ff1b6eaa7', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER], - 'ortb2Imp': { - 'ext': { - 'gpid': '0123456789', - 'tid': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf' + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + ortb2Imp: { + ext: { + gpid: '0123456789', + tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf' } } }; const VIDEO_BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', - 'bidderRequestId': '12a8ae9ada9c13', - 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '635509f7ff6642d368cb9837', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1, - 'placementId': 'testBanner' + bidId: '2d52001cabd528', + bidder: 'screencore', + adUnitCode: 'video-ad-unit', + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + params: { + placementId: 'testVideoPlacement', + endpointId: 'testVideoEndpoint' }, - '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 + 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 } }, - 'ortb2Imp': { - 'ext': { - 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + ortb2Imp: { + ext: { + tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee' } } -} - -const ORTB2_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 NATIVE_BID = { + bidId: '2d52001cabd529', + bidder: 'screencore', + adUnitCode: 'native-ad-unit', + transactionId: '77e184c6-bde9-497b-b9b9-cf47a61381ee', + params: { + placementId: 'testNativePlacement' }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + mediaTypes: { + native: { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: false } + } + } }; 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' + refererInfo: { + page: 'https://www.example.com', + ref: 'https://www.referrer.com' }, - 'ortb2': { - 'site': { - 'content': { - 'language': 'en' - } - }, - 'regs': { - 'gpp': 'gpp_string', - 'gpp_sid': [7], - 'coppa': 0 - }, - 'device': ORTB2_DEVICE, + ortb2: { + device: { + w: 1920, + h: 1080, + language: 'en' + } } }; 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' - }] - }] - } + body: [{ + requestId: '2d52001cabd527', + cpm: 0.8, + creativeId: '12610997325162499419', + ttl: 30, + currency: 'USD', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + adomain: ['securepubads.g.doubleclick.net'] + }] }; const VIDEO_SERVER_RESPONSE = { - body: { - 'cid': '635509f7ff6642d368cb9837', - 'results': [{ - 'ad': '', - 'advertiserDomains': ['screencore.io'], - 'exp': 60, - 'width': 545, - 'height': 307, - 'mediaType': 'video', - 'creativeId': '12610997325162499419', - 'price': 2, - 'cookies': [] - }] - } -}; - -const ORTB2_OBJ = { - "device": ORTB2_DEVICE, - "regs": {"coppa": 0, "gpp": "gpp_string", "gpp_sid": [7]}, - "site": {"content": {"language": "en"} - } + body: [{ + requestId: '2d52001cabd528', + cpm: 2, + creativeId: '12610997325162499419', + ttl: 60, + currency: 'USD', + width: 545, + height: 307, + mediaType: 'video', + vastXml: '', + adomain: ['screencore.io'] + }] }; const REQUEST = { data: { - width: 300, - height: 250, - bidId: '2d52001cabd527' + placements: [{ + bidId: '2d52001cabd527', + adFormat: 'banner', + sizes: [[300, 250], [300, 600]] + }] } }; -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - describe('screencore bid adapter', function () { before(() => config.resetConfig()); after(() => config.resetConfig()); describe('validate spec', function () { - it('exists and is a function', function () { + it('should have isBidRequestValid as a function', function () { expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); }); - it('exists and is a function', function () { + it('should have buildRequests as a function', function () { expect(adapter.buildRequests).to.exist.and.to.be.a('function'); }); - it('exists and is a function', function () { + it('should have interpretResponse as a function', function () { expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); }); - it('exists and is a function', function () { + it('should have getUserSyncs as a function', function () { expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); }); - it('exists and is a string', function () { + it('should have code as a string', function () { expect(adapter.code).to.exist.and.to.be.a('string'); + expect(adapter.code).to.equal('screencore'); }); - it('exists and contains media types', function () { + it('should have supportedMediaTypes with BANNER, VIDEO, NATIVE', function () { expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(3); expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO, NATIVE]); }); + + it('should have gvlid', function () { + expect(adapter.gvlid).to.exist.and.to.equal(1473); + }); + + it('should have version', function () { + expect(adapter.version).to.exist.and.to.equal('1.0.0'); + }); }); describe('validate bid requests', function () { - it('should require cId', function () { + it('should return false when placementId and endpointId are missing', function () { const isValid = adapter.isBidRequestValid({ - params: { - pId: 'pid', - }, + bidId: '123', + params: {}, + mediaTypes: { banner: { sizes: [[300, 250]] } } }); expect(isValid).to.be.false; }); - it('should require pId', function () { + it('should return false when mediaTypes is missing', function () { const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - }, + bidId: '123', + params: { placementId: 'test' } }); expect(isValid).to.be.false; }); - it('should validate correctly', function () { + it('should return true when placementId is present with banner mediaType', function () { const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - pId: 'pid', - }, + bidId: '123', + params: { placementId: 'test' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }); + expect(isValid).to.be.true; + }); + + it('should return true when endpointId is present with banner mediaType', function () { + const isValid = adapter.isBidRequestValid({ + bidId: '123', + params: { endpointId: 'test' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }); + expect(isValid).to.be.true; + }); + + it('should return true when placementId is present with video mediaType', function () { + const isValid = adapter.isBidRequestValid({ + bidId: '123', + params: { placementId: 'test' }, + mediaTypes: { video: { playerSize: [[640, 480]] } } + }); + expect(isValid).to.be.true; + }); + + it('should return true when placementId is present with native mediaType', function () { + const isValid = adapter.isBidRequestValid({ + bidId: '123', + params: { placementId: 'test' }, + mediaTypes: { native: { title: { required: true } } } }); expect(isValid).to.be.true; }); }); describe('build requests', function () { - let sandbox; - before(function () { - getGlobal().bidderSettings = { - screencore: { - storageAllowed: true, - }, - }; - sandbox = sinon.createSandbox(); - sandbox.stub(Date, 'now').returns(1000); + it('should build banner request', function () { + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.exist; + expect(requests.method).to.equal('POST'); + expect(requests.url).to.include('screencore.io/prebid'); + expect(requests.data).to.exist; + expect(requests.data.placements).to.be.an('array'); + expect(requests.data.placements[0].bidId).to.equal(BID.bidId); + expect(requests.data.placements[0].adFormat).to.equal(BANNER); }); 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()}/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', - 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': '' - }, - device: ORTB2_DEVICE, - 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: '', - cat: [], - contentLang: 'en', - contentData: [], - isStorageAllowed: true, - pagecat: [], - ortb2Imp: VIDEO_BID.ortb2Imp, - ortb2: ORTB2_OBJ, - placementId: "testBanner", - userData: [], - coppa: 0 - } - }); + expect(requests).to.exist; + expect(requests.method).to.equal('POST'); + expect(requests.data.placements).to.be.an('array'); + expect(requests.data.placements[0].bidId).to.equal(VIDEO_BID.bidId); + expect(requests.data.placements[0].adFormat).to.equal(VIDEO); }); - 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', - 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': '' - }, - device: ORTB2_DEVICE, - 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: '0123456789', - uqs: getTopWindowQueryParams(), - 'ext.param1': 'loremipsum', - 'ext.param2': 'dolorsitamet', - cat: [], - contentLang: 'en', - contentData: [], - isStorageAllowed: true, - pagecat: [], - ortb2Imp: BID.ortb2Imp, - ortb2: ORTB2_OBJ, - placementId: "testBanner", - userData: [], - coppa: 0 - } - }); + it('should build native request', function () { + const requests = adapter.buildRequests([NATIVE_BID], BIDDER_REQUEST); + expect(requests).to.exist; + expect(requests.data.placements).to.be.an('array'); + expect(requests.data.placements[0].bidId).to.equal(NATIVE_BID.bidId); + expect(requests.data.placements[0].adFormat).to.equal(NATIVE); }); - after(function () { - getGlobal().bidderSettings = {}; - sandbox.restore(); + it('should include gpid when available', function () { + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests.data.placements[0].gpid).to.equal('0123456789'); }); - }); - describe('getUserSyncs', function () { - it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + it('should include placementId in placement when present', function () { + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests.data.placements[0].placementId).to.equal('testPlacement'); + expect(requests.data.placements[0].type).to.equal('publisher'); + }); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.screencore.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - }]); + it('should include endpointId in placement when placementId is not present', function () { + const bidWithEndpoint = { + bidId: '2d52001cabd530', + bidder: 'screencore', + adUnitCode: 'div-gpt-ad-endpoint', + transactionId: 'd881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + params: { + endpointId: 'testEndpointOnly' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + const requests = adapter.buildRequests([bidWithEndpoint], BIDDER_REQUEST); + expect(requests.data.placements[0].endpointId).to.equal('testEndpointOnly'); + expect(requests.data.placements[0].type).to.equal('network'); }); + }); - it('should have valid user sync with cid on response', function () { + describe('getUserSyncs', function () { + it('should return iframe sync when iframeEnabled', function () { + config.setConfig({ coppa: 0 }); const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.screencore.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - }]); + expect(result).to.be.an('array').with.length(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.include('https://cs.screencore.io/iframe?pbjs=1'); }); - it('should have valid user sync with pixelEnabled', function () { + it('should return image sync when pixelEnabled', function () { + config.setConfig({ coppa: 0 }); const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); - - expect(result).to.deep.equal([{ - 'url': 'https://cs.screencore.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - 'type': 'image', - }]); + expect(result).to.be.an('array').with.length(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.include('https://cs.screencore.io/image?pbjs=1'); }); - it('should have valid user sync with coppa 1 on response', function () { - config.setConfig({ - coppa: 1, - }); + it('should include coppa parameter', function () { + config.setConfig({ coppa: 1 }); const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.screencore.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1', - }]); + expect(result[0].url).to.include('coppa=1'); }); - it('should generate url with consent data', function () { + it('should include gdpr consent when provided', function () { + config.setConfig({ coppa: 0 }); const gdprConsent = { gdprApplies: true, - consentString: 'consent_string', + consentString: 'consent_string' }; - const uspConsent = 'usp_string'; + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE], gdprConsent); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=consent_string'); + }); + + it('should include gpp consent when provided', function () { + config.setConfig({ coppa: 0 }); const gppConsent = { gppString: 'gpp_string', - applicableSections: [7], + applicableSections: [7] }; - - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); - - expect(result).to.deep.equal([{ - 'url': 'https://cs.screencore.io/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&coppa=1&gpp=gpp_string&gpp_sid=7', - 'type': 'image', - }]); + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE], null, null, gppConsent); + expect(result[0].url).to.include('gpp=gpp_string'); + expect(result[0].url).to.include('gpp_sid=7'); }); }); 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' }); + it('should return empty array when body is empty array', function () { + const responses = adapter.interpretResponse({ body: [] }); 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: ['screencore.io'], - agencyName: 'Agency Name', - }; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses[0].meta).to.deep.equal({ - advertiserDomains: ['screencore.io'], - agencyName: 'Agency Name', - }); + expect(responses[0].requestId).to.equal('2d52001cabd527'); + expect(responses[0].cpm).to.equal(0.8); + expect(responses[0].width).to.equal(300); + expect(responses[0].height).to.equal(250); + expect(responses[0].creativeId).to.equal('12610997325162499419'); + expect(responses[0].currency).to.equal('USD'); + expect(responses[0].ttl).to.equal(30); + expect(responses[0].ad).to.equal(''); + expect(responses[0].meta.advertiserDomains).to.deep.equal(['securepubads.g.doubleclick.net']); }); 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: ['screencore.io'], - }, - }); - }); - - 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); + expect(responses[0].requestId).to.equal('2d52001cabd528'); + expect(responses[0].cpm).to.equal(2); + expect(responses[0].width).to.equal(545); + expect(responses[0].height).to.equal(307); + expect(responses[0].mediaType).to.equal('video'); + expect(responses[0].vastXml).to.equal(''); }); }); - 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 '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('createDomain test', function () { + it('should return correct domain for US timezone', function () { + const stub = sinon.stub(Intl, 'DateTimeFormat').returns({ + resolvedOptions: () => ({ timeZone: 'America/New_York' }) }); - }); - // testing bid.userIdAsEids handling - it("should include user ids from bid.userIdAsEids (length=1)", function() { - const bid = utils.deepClone(BID); - bid.userIdAsEids = [ - { - "source": "audigent.com", - "uids": [{"id": "fakeidi6j6dlc6e"}] - } - ] - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); - }) - it("should include user ids from bid.userIdAsEids (length=2)", function() { - const bid = utils.deepClone(BID); - bid.userIdAsEids = [ - { - "source": "audigent.com", - "uids": [{"id": "fakeidi6j6dlc6e"}] - }, - { - "source": "rwdcntrl.net", - "uids": [{"id": "fakeid6f35197d5c", "atype": 1}] - } - ] - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); - expect(requests[0].data['uid.rwdcntrl.net']).to.equal("fakeid6f35197d5c"); - }) - // testing user.ext.eid handling - it("should include user ids from user.ext.eid (length=1)", function() { - const bid = utils.deepClone(BID); - bid.user = { - ext: { - eids: [ - { - "source": "pubcid.org", - "uids": [{"id": "fakeid8888dlc6e"}] - } - ] - } - } - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); - }) - it("should include user ids from user.ext.eid (length=2)", function() { - const bid = utils.deepClone(BID); - bid.user = { - ext: { - eids: [ - { - "source": "pubcid.org", - "uids": [{"id": "fakeid8888dlc6e"}] - }, - { - "source": "adserver.org", - "uids": [{"id": "fakeid495ff1"}] - } - ] - } - } - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); - expect(requests[0].data['uid.adserver.org']).to.equal("fakeid495ff1"); - }) - }); - - 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 () { - getGlobal().bidderSettings = { - screencore: { - storageAllowed: true, - }, - }; - }); - after(function () { - getGlobal().bidderSettings = {}; - }); - const key = 'myKey'; - let uniqueDealId; - beforeEach(() => { - uniqueDealId = getUniqueDealId(storage, key, 0); - }); + const domain = createDomain(); + expect(domain).to.equal('https://taqus.screencore.io'); - it('should get current unique deal id', function (done) { - // waiting some time so `now` will become past - setTimeout(() => { - const current = getUniqueDealId(storage, key); - expect(current).to.be.equal(uniqueDealId); - done(); - }, 200); - }); - - it('should get new unique deal id on expiration', function (done) { - setTimeout(() => { - const current = getUniqueDealId(storage, key, 100); - expect(current).to.not.be.equal(uniqueDealId); - done(); - }, 200); + stub.restore(); }); - }); - describe('storage utils', function () { - before(function () { - getGlobal().bidderSettings = { - screencore: { - storageAllowed: true, - }, - }; - }); - after(function () { - getGlobal().bidderSettings = {}; - }); - it('should get value from storage with create param', function () { - const now = Date.now(); - const clock = useFakeTimers({ - shouldAdvanceTime: true, - now, + it('should return correct domain for EU timezone', function () { + const stub = sinon.stub(Intl, 'DateTimeFormat').returns({ + resolvedOptions: () => ({ timeZone: 'Europe/London' }) }); - 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'); - 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(storage, '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'); - }); + const domain = createDomain(); + expect(domain).to.equal('https://taqeu.screencore.io'); - 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); + stub.restore(); }); - }); - describe('createDomain test', function () { - it('should return correct domain', function () { + it('should return correct domain for APAC timezone', function () { const stub = sinon.stub(Intl, 'DateTimeFormat').returns({ - resolvedOptions: () => ({ timeZone: 'America/New_York' }), + resolvedOptions: () => ({ timeZone: 'Asia/Tokyo' }) }); - const responses = createDomain(); - expect(responses).to.be.equal('https://taqus.screencore.io'); + const domain = createDomain(); + expect(domain).to.equal('https://taqapac.screencore.io'); stub.restore(); }); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index db65b3dcbc7..3a448c90d78 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -31,7 +31,7 @@ function getSlotConfigs(mediaTypes, params) { bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', - bidRequestsCount: 1, + bidderRequestsCount: 1, bidder: 'seedtag', mediaTypes: mediaTypes, src: 'client', diff --git a/test/spec/modules/seenthisBrandStories_spec.js b/test/spec/modules/seenthisBrandStories_spec.js new file mode 100644 index 00000000000..c565a33fa88 --- /dev/null +++ b/test/spec/modules/seenthisBrandStories_spec.js @@ -0,0 +1,333 @@ +import { expect } from "chai"; +import { + addStyleToSingleChildAncestors, + applyAutoHeight, + applyFullWidth, + calculateMargins, + DEFAULT_MARGINS, + findAdWrapper, + getFrameByEvent, + SEENTHIS_EVENTS, +} from "modules/seenthisBrandStories.ts"; +import * as boundingClientRect from "../../../libraries/boundingClientRect/boundingClientRect.js"; +import * as utils from "../../../src/utils.js"; +import * as winDimensions from "src/utils/winDimensions.js"; + +describe("seenthisBrandStories", function () { + describe("constants", function () { + it("should have correct DEFAULT_MARGINS", function () { + expect(DEFAULT_MARGINS).to.equal("16px"); + }); + + it("should have correct SEENTHIS_EVENTS array", function () { + expect(SEENTHIS_EVENTS).to.be.an("array").with.length(9); + expect(SEENTHIS_EVENTS).to.include("@seenthis_storylines/ready"); + expect(SEENTHIS_EVENTS).to.include("@seenthis_enabled"); + expect(SEENTHIS_EVENTS).to.include("@seenthis_modal/opened"); + }); + }); + + describe("calculateMargins", function () { + let mockElement; + let getBoundingClientRectStub; + let getComputedStyleStub; + + beforeEach(function () { + mockElement = { + style: { + setProperty: sinon.stub(), + }, + }; + + getBoundingClientRectStub = sinon.stub( + boundingClientRect, + "getBoundingClientRect" + ); + getComputedStyleStub = sinon.stub(window, "getComputedStyle"); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should set margins correctly with non-zero values", function () { + getBoundingClientRectStub.returns({ left: 32, width: 300 }); + getComputedStyleStub.returns({ marginLeft: "16px" }); + + calculateMargins(mockElement); + + expect( + mockElement.style.setProperty.calledWith( + "--storylines-margin-left", + "-16px" + ) + ).to.be.true; + expect( + mockElement.style.setProperty.calledWith("--storylines-margins", "32px") + ).to.be.true; + }); + + it("should use default margins when width is 0", function () { + getBoundingClientRectStub.returns({ left: 16, width: 0 }); + getComputedStyleStub.returns({ marginLeft: "0px" }); + + calculateMargins(mockElement); + + expect( + mockElement.style.setProperty.calledWith("--storylines-margins", "16px") + ).to.be.true; + expect( + mockElement.style.setProperty.calledWith( + "--storylines-margin-left", + "16px" + ) + ).to.be.true; + }); + + it("should use default margins when margin left is 0", function () { + getBoundingClientRectStub.returns({ left: 16, width: 300 }); + getComputedStyleStub.returns({ marginLeft: "16px" }); + + calculateMargins(mockElement); + + expect( + mockElement.style.setProperty.calledWith("--storylines-margins", "16px") + ).to.be.true; + expect( + mockElement.style.setProperty.calledWith( + "--storylines-margin-left", + "16px" + ) + ).to.be.true; + }); + }); + + describe("getFrameByEvent", function () { + let getElementsByTagNameStub; + let mockIframes; + let mockEventSource; + + beforeEach(function () { + mockEventSource = { id: "frame2" }; + + mockIframes = [ + { contentWindow: { id: "frame1" } }, + { contentWindow: mockEventSource }, // This will match + { contentWindow: { id: "frame3" } }, + ]; + + getElementsByTagNameStub = sinon + .stub(document, "getElementsByTagName") + .returns(mockIframes); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should return iframe matching event source", function () { + const mockEvent = { + source: mockEventSource, // This should match mockIframes[1].contentWindow + }; + + const result = getFrameByEvent(mockEvent); + + expect(result).to.equal(mockIframes[1]); + expect(getElementsByTagNameStub.calledWith("iframe")).to.be.true; + }); + + it("should return undefined if no iframe matches", function () { + const mockEvent = { + source: { id: "nonexistent" }, // This won't match any iframe + }; + + const result = getFrameByEvent(mockEvent); + + expect(result).to.be.null; + }); + }); + + describe("addStyleToSingleChildAncestors", function () { + beforeEach(function () { + sinon + .stub(winDimensions, "getWinDimensions") + .returns({ innerWidth: 1024, innerHeight: 768 }); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should apply style to element when width is less than window width", function () { + const mockElement = { + style: { + setProperty: sinon.stub(), + width: "", // key exists + }, + offsetWidth: 400, + parentElement: null, + }; + + addStyleToSingleChildAncestors(mockElement, { + key: "width", + value: "100%", + }); + + expect(mockElement.style.setProperty.calledWith("width", "100%")).to.be + .true; + }); + + it("should not apply style when element width equals window width", function () { + const mockElement = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 1024, + parentElement: null, + }; + + addStyleToSingleChildAncestors(mockElement, { + key: "width", + value: "100%", + }); + + expect(mockElement.style.setProperty.called).to.be.false; + }); + + it("should recursively apply to single child ancestors", function () { + const grandParent = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 800, + parentElement: null, + children: { length: 1 }, + }; + + const parent = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 600, + parentElement: grandParent, + children: { length: 1 }, + }; + + const child = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 400, + parentElement: parent, + }; + + addStyleToSingleChildAncestors(child, { key: "width", value: "100%" }); + + expect(child.style.setProperty.calledWith("width", "100%")).to.be.true; + expect(parent.style.setProperty.calledWith("width", "100%")).to.be.true; + expect(grandParent.style.setProperty.calledWith("width", "100%")).to.be + .true; + }); + + it("should stop recursion when parent has multiple children", function () { + const parent = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 600, + parentElement: null, + children: { length: 2 }, // Multiple children + }; + + const child = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 400, + parentElement: parent, + }; + + addStyleToSingleChildAncestors(child, { key: "width", value: "100%" }); + + expect(child.style.setProperty.calledWith("width", "100%")).to.be.true; + expect(parent.style.setProperty.called).to.be.false; + }); + + it("should not apply style when key is not in element style", function () { + const mockElement = { + style: { + setProperty: sinon.stub(), + // 'width' key not present + }, + offsetWidth: 400, + parentElement: null, + }; + + addStyleToSingleChildAncestors(mockElement, { + key: "width", + value: "100%", + }); + + expect(mockElement.style.setProperty.called).to.be.false; + }); + }); + + describe("findAdWrapper", function () { + it("should return grandparent element", function () { + const grandParent = {}; + const parent = { parentElement: grandParent }; + const target = { parentElement: parent }; + + const result = findAdWrapper(target); + + expect(result).to.equal(grandParent); + }); + }); + + describe("applyFullWidth", function () { + let findAdWrapperStub; + let addStyleToSingleChildAncestorsStub; + + beforeEach(function () { + findAdWrapperStub = sinon.stub(); + addStyleToSingleChildAncestorsStub = sinon.stub(); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should call addStyleToSingleChildAncestors with width 100% when adWrapper exists", function () { + const mockTarget = {}; + + expect(() => applyFullWidth(mockTarget)).to.not.throw(); + }); + + it("should handle null adWrapper gracefully", function () { + const mockTarget = {}; + + expect(() => applyFullWidth(mockTarget)).to.not.throw(); + }); + }); + + describe("applyAutoHeight", function () { + it("should call addStyleToSingleChildAncestors with height auto when adWrapper exists", function () { + const mockTarget = {}; + + // Test that function executes without errors + expect(() => applyAutoHeight(mockTarget)).to.not.throw(); + }); + + it("should handle null adWrapper gracefully", function () { + const mockTarget = {}; + + expect(() => applyAutoHeight(mockTarget)).to.not.throw(); + }); + }); +}); diff --git a/test/spec/modules/sevioBidAdapter_spec.js b/test/spec/modules/sevioBidAdapter_spec.js index a7a56d798a9..ce03c1baf12 100644 --- a/test/spec/modules/sevioBidAdapter_spec.js +++ b/test/spec/modules/sevioBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/sevioBidAdapter.js'; - +import { config } from 'src/config.js'; const ENDPOINT_URL = 'https://req.adx.ws/prebid'; describe('sevioBidAdapter', function () { @@ -326,7 +326,7 @@ describe('sevioBidAdapter', function () { it('getPageTitle prefers top.title; falls back to og:title (top document)', () => { window.top.document.title = 'Doc Title'; let out = spec.buildRequests([mkBid()], baseBidderRequest); - expect(out[0].data.pageTitle).to.equal('Doc Title'); + expect(out[0].data.context[0].text).to.equal('Doc Title'); window.top.document.title = ''; const meta = window.top.document.createElement('meta'); @@ -335,7 +335,7 @@ describe('sevioBidAdapter', function () { window.top.document.head.appendChild(meta); out = spec.buildRequests([mkBid()], baseBidderRequest); - expect(out[0].data.pageTitle).to.equal('OG Title'); + expect(out[0].data.context[0].text).to.equal('OG Title'); meta.remove(); }); @@ -352,7 +352,7 @@ describe('sevioBidAdapter', function () { get() { throw new Error('cross-origin'); } }); const out = spec.buildRequests([mkBid()], baseBidderRequest); - expect(out[0].data.pageTitle).to.equal('Local Title'); + expect(out[0].data.context[0].text).to.equal('Local Title'); Object.defineProperty(window, 'top', original); restored = true; } catch (e) { @@ -446,4 +446,183 @@ describe('sevioBidAdapter', function () { ]); }); }); + + describe('currency handling', function () { + let bidRequests; + let bidderRequests; + + beforeEach(function () { + bidRequests = [{ + bidder: 'sevio', + params: { zone: 'zoneId' }, + mediaTypes: { banner: { sizes: [[300, 250]] } }, + bidId: '123' + }]; + + bidderRequests = { + refererInfo: { + referer: 'https://example.com', + page: 'https://example.com', + } + }; + }); + + afterEach(function () { + if (typeof config.resetConfig === 'function') { + config.resetConfig(); + } else if (typeof config.setConfig === 'function') { + config.setConfig({ currency: null }); + } + }); + + it('includes EUR currency when EUR is set in prebid config', function () { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + + const req = spec.buildRequests(bidRequests, bidderRequests); + const payload = req[0].data; + + expect(payload.currency).to.equal('EUR'); + }); + + it('includes GBP currency when GBP is set in prebid config', function () { + config.setConfig({ + currency: { + adServerCurrency: 'GBP' + } + }); + + const req = spec.buildRequests(bidRequests, bidderRequests); + const payload = req[0].data; + + expect(payload.currency).to.equal('GBP'); + }); + + it('does NOT include currency when no currency config is set', function () { + const req = spec.buildRequests(bidRequests, bidderRequests); + const payload = req[0].data; + + expect(payload).to.not.have.property('currency'); + }); + + it('parses comma-separated keywords string into tokens array', function () { + const singleBidRequest = [{ + bidder: 'sevio', + params: { zone: 'zoneId', keywords: 'play, games, fun ' }, // string CSV + mediaTypes: { banner: { sizes: [[728, 90]] } }, + bidId: 'bid-kw-str' + }]; + const requests = spec.buildRequests(singleBidRequest, baseBidderRequest); + expect(requests).to.be.an('array').that.is.not.empty; + expect(requests[0].data).to.have.nested.property('keywords.tokens'); + expect(requests[0].data.keywords.tokens).to.deep.equal(['play', 'games', 'fun']); + }); + }); + + describe('native parsing', function () { + it('parses native assets: title, data->desc (type 2), image (asset.img), clickUrl and trackers', function () { + const serverResponseNative = { + body: { + bids: [ + { + requestId: 'native-1', + cpm: 1.0, + currency: 'EUR', + width: 1, + height: 1, + creativeId: 'native-creative-1', + ad: JSON.stringify({ + ver: '1.2', + assets: [ + { id: 2, title: { text: 'Native Title' } }, + { id: 4, data: { type: 2, value: 'Native body text' } }, + { id: 5, img: { type: 3, url: 'https://img.example/x.png', w: 120, h: 60 } } + ], + eventtrackers: [ + { event: 1, method: 1, url: 'https://impr.example/1' }, + { event: 2, method: 1, url: 'https://view.example/1' } + ], + link: { url: 'https://click.example', clicktrackers: ['https://clickt.example/1'] } + }), + ttl: 300, + netRevenue: true, + mediaType: 'NATIVE', + meta: { advertiserDomains: ['adv.example'] }, + bidder: 'sevio' + } + ] + } + }; + + const result = spec.interpretResponse(serverResponseNative); + expect(result).to.be.an('array').with.lengthOf(1); + + const out = result[0]; + expect(out).to.have.property('native'); + + const native = out.native; + expect(native.title).to.equal('Native Title'); + expect(native.image).to.equal('https://img.example/x.png'); + expect(native.image_width).to.equal(120); + expect(native.image_height).to.equal(60); + expect(native.clickUrl).to.equal('https://click.example'); + + expect(native.impressionTrackers).to.be.an('array').that.includes('https://impr.example/1'); + expect(native.viewableTrackers).to.be.an('array').that.includes('https://view.example/1'); + expect(native.clickTrackers).to.be.an('array').that.includes('https://clickt.example/1'); + + // meta preserved + expect(out.meta).to.have.property('advertiserDomains').that.deep.equals(['adv.example']); + }); + + it('maps legacy asset.id -> image types (13 -> icon, 14 -> image) and sets icon fields', function () { + const serverResponseIcon = { + body: { + bids: [ + { + requestId: 'native-icon', + cpm: 1.0, + currency: 'EUR', + width: 1, + height: 1, + creativeId: 'native-creative-icon', + ad: JSON.stringify({ + ver: '1.2', + assets: [ + // legacy asset id 13 should map to icon (img type 1) + { id: 13, img: { url: 'https://img.example/icon.png', w: 50, h: 50 } }, + // legacy asset id 14 should map to image (img type 3) + { id: 14, img: { url: 'https://img.example/img.png', w: 200, h: 100 } }, + { id: 2, title: { text: 'Legacy Mapping Test' } } + ], + link: { url: 'https://click.example/leg' } + }), + ttl: 300, + netRevenue: true, + mediaType: 'NATIVE', + meta: { advertiserDomains: ['legacy.example'] }, + bidder: 'sevio' + } + ] + } + }; + + const result = spec.interpretResponse(serverResponseIcon); + expect(result).to.be.an('array').with.lengthOf(1); + const native = result[0].native; + + // icon mapped from id 13 + expect(native.icon).to.equal('https://img.example/icon.png'); + expect(native.icon_width).to.equal(50); + expect(native.icon_height).to.equal(50); + + // image mapped from id 14 + expect(native.image).to.equal('https://img.example/img.png'); + expect(native.image_width).to.equal(200); + expect(native.image_height).to.equal(100); + }); + }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index dfe9c85e3a3..56f238ff1ba 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -895,43 +895,6 @@ describe('sharethrough adapter spec', function () { }); }); - describe('cookie deprecation', () => { - it('should not add cdep if we do not get it in an impression request', () => { - const builtRequests = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - ortb2: { - device: { - ext: { - propThatIsNotCdep: 'value-we-dont-care-about', - }, - }, - }, - }); - const noCdep = builtRequests.every((builtRequest) => { - const ourCdepValue = builtRequest.data.device?.ext?.cdep; - return ourCdepValue === undefined; - }); - expect(noCdep).to.be.true; - }); - - it('should add cdep if we DO get it in an impression request', () => { - const builtRequests = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - ortb2: { - device: { - ext: { - cdep: 'cdep-value', - }, - }, - }, - }); - const cdepPresent = builtRequests.every((builtRequest) => { - return builtRequest.data.device.ext.cdep === 'cdep-value'; - }); - expect(cdepPresent).to.be.true; - }); - }); - describe('first party data', () => { const firstPartyData = { site: { diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index 29607365c68..19848ffd03f 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -446,7 +446,7 @@ describe('SmartHubBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -455,9 +455,7 @@ describe('SmartHubBidAdapter', function () { expect(syncData[0].url).to.equal('https://us4.shb-sync.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0&pid=360') }); it('Should return array of objects with CCPA values', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -466,7 +464,7 @@ describe('SmartHubBidAdapter', function () { expect(syncData[0].url).to.equal('https://us4.shb-sync.com/image?pbjs=1&ccpa_consent=1---&coppa=0&pid=360') }); it('Should return array of objects with GPP values', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'ab12345', applicableSections: [8] }); @@ -478,7 +476,7 @@ describe('SmartHubBidAdapter', function () { expect(syncData[0].url).to.equal('https://us4.shb-sync.com/image?pbjs=1&gpp=ab12345&gpp_sid=8&coppa=0&pid=360') }); it('Should return iframe type if iframeEnabled is true', function() { - const syncData = spec.getUserSyncs({iframeEnabled: true}, {}, {}, {}, {}); + const syncData = spec.getUserSyncs({iframeEnabled: true}, {}, {}, undefined, {}); 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') diff --git a/test/spec/modules/smootBidAdapter_spec.js b/test/spec/modules/smootBidAdapter_spec.js index d9c5580e60d..7ab8100bbe1 100644 --- a/test/spec/modules/smootBidAdapter_spec.js +++ b/test/spec/modules/smootBidAdapter_spec.js @@ -544,7 +544,7 @@ describe('SmootBidAdapter', function () { consentString: 'ALL', gdprApplies: true, }, - {} + undefined ); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object'); @@ -560,9 +560,7 @@ describe('SmootBidAdapter', function () { {}, {}, {}, - { - consentString: '1---', - } + '1---' ); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object'); @@ -578,7 +576,7 @@ describe('SmootBidAdapter', function () { {}, {}, {}, - {}, + undefined, { gppString: 'abc123', applicableSections: [8], diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 58608705073..c442b1d53db 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -240,55 +240,6 @@ describe('sovrnBidAdapter', function() { expect(payload.imp[0]?.ext?.tid).to.equal('1a2c032473f4983') }) - it('when FLEDGE is enabled, should send ortb2imp.ext.ae', function () { - const bidderRequest = { - ...baseBidderRequest, - paapi: {enabled: true} - } - const bidRequest = { - ...baseBidRequest, - ortb2Imp: { - ext: { - ae: 1 - } - }, - } - const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data) - expect(payload.imp[0].ext.ae).to.equal(1) - }) - - it('when FLEDGE is not enabled, should not send ortb2imp.ext.ae', function () { - const bidRequest = { - ...baseBidRequest, - ortb2Imp: { - ext: { - ae: 1 - } - }, - } - const payload = JSON.parse(spec.buildRequests([bidRequest], baseBidderRequest).data) - expect(payload.imp[0].ext.ae).to.be.undefined - }) - - it('when FLEDGE is enabled, but env is malformed, should not send ortb2imp.ext.ae', function () { - const bidderRequest = { - ...baseBidderRequest, - paapi: { - enabled: true - } - } - const bidRequest = { - ...baseBidRequest, - ortb2Imp: { - ext: { - ae: 'malformed' - } - }, - } - const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data) - expect(payload.imp[0].ext.ae).to.be.undefined - }) - it('includes the ad unit code in the request', function() { const impression = payload.imp[0] expect(impression.adunitcode).to.equal('adunit-code') @@ -961,158 +912,6 @@ describe('sovrnBidAdapter', function() { }) }) - describe('fledge response', function () { - const fledgeResponse = { - body: { - id: '37386aade21a71', - seatbid: [{ - bid: [{ - id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', - crid: 'creativelycreatedcreativecreative', - impid: '263c448586f5a1', - price: 0.45882675, - nurl: '', - adm: '', - h: 90, - w: 728 - }] - }], - ext: { - seller: 'seller.lijit.com', - decisionLogicUrl: 'https://decision.lijit.com', - igbid: [{ - impid: 'test_imp_id', - igbuyer: [{ - igdomain: 'ap.lijit.com', - buyerdata: { - base_bid_micros: 0.1, - use_bid_multiplier: true, - multiplier: '1.3' - } - }, { - igdomain: 'buyer2.com', - buyerdata: {} - }, { - igdomain: 'buyer3.com', - buyerdata: {} - }] - }, { - impid: 'test_imp_id_2', - igbuyer: [{ - igdomain: 'ap2.lijit.com', - buyerdata: { - base_bid_micros: '0.2', - } - }] - }, { - impid: '', - igbuyer: [{ - igdomain: 'ap3.lijit.com', - buyerdata: { - base_bid_micros: '0.3', - } - }] - }, { - impid: 'test_imp_id_3', - igbuyer: [{ - igdomain: '', - buyerdata: { - base_bid_micros: '0.3', - } - }] - }, { - impid: 'test_imp_id_4', - igbuyer: [] - }] - } - } - } - const emptyFledgeResponse = { - body: { - id: '37386aade21a71', - seatbid: [{ - bid: [{ - id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', - crid: 'creativelycreatedcreativecreative', - impid: '263c448586f5a1', - price: 0.45882675, - nurl: '', - adm: '', - h: 90, - w: 728 - }] - }], - ext: { - igbid: { - } - } - } - } - const expectedResponse = { - requestId: '263c448586f5a1', - cpm: 0.45882675, - width: 728, - height: 90, - creativeId: 'creativelycreatedcreativecreative', - dealId: null, - currency: 'USD', - netRevenue: true, - mediaType: 'banner', - ttl: 60000, - meta: { advertiserDomains: [] }, - ad: decodeURIComponent(`>`) - } - const expectedFledgeResponse = [ - { - bidId: 'test_imp_id', - config: { - seller: 'seller.lijit.com', - decisionLogicUrl: 'https://decision.lijit.com', - sellerTimeout: undefined, - auctionSignals: {}, - interestGroupBuyers: ['ap.lijit.com', 'buyer2.com', 'buyer3.com'], - perBuyerSignals: { - 'ap.lijit.com': { - base_bid_micros: 0.1, - use_bid_multiplier: true, - multiplier: '1.3' - }, - 'buyer2.com': {}, - 'buyer3.com': {} - } - } - }, - { - bidId: 'test_imp_id_2', - config: { - seller: 'seller.lijit.com', - decisionLogicUrl: 'https://decision.lijit.com', - sellerTimeout: undefined, - auctionSignals: {}, - interestGroupBuyers: ['ap2.lijit.com'], - perBuyerSignals: { - 'ap2.lijit.com': { - base_bid_micros: '0.2', - } - } - } - } - ] - - 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('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) - expect(result.length).to.equal(1) - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) - }) - }) - describe('interpretResponse video', function () { let videoResponse const bidAdm = 'key%3Dvalue' diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 8219ec3e8e2..8c23be4bcd0 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec, internal, END_POINT_URL, userData, EVENT_ENDPOINT} from 'modules/taboolaBidAdapter.js'; +import {spec, internal, END_POINT_URL, userData, EVENT_ENDPOINT, detectBot, getPageVisibility} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config.js' import * as utils from '../../../src/utils.js' import {server} from '../../mocks/xhr.js' @@ -116,6 +116,89 @@ describe('Taboola Adapter', function () { spec.onBidWon(bid); expect(server.requests[0].url).to.equals('http://win.example.com/3.4') }); + + it('should not fire nurl when deferBilling is true', function () { + const nurl = 'http://win.example.com/${AUCTION_PRICE}'; + const bid = { + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250, + nurl: nurl, + deferBilling: true + } + spec.onBidWon(bid); + expect(server.requests.length).to.equal(0); + }); + }); + + describe('onBidBillable', function () { + it('onBidBillable exist as a function', () => { + expect(spec.onBidBillable).to.exist.and.to.be.a('function'); + }); + + it('should fire burl when available', function () { + const burl = 'http://billing.example.com/${AUCTION_PRICE}'; + const nurl = 'http://win.example.com/${AUCTION_PRICE}'; + const bid = { + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250, + nurl: nurl, + burl: burl + } + spec.onBidBillable(bid); + expect(server.requests[0].url).to.equals('http://billing.example.com/3.4'); + }); + + it('should fall back to nurl when burl is not available', function () { + const nurl = 'http://win.example.com/${AUCTION_PRICE}'; + const bid = { + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250, + nurl: nurl + } + spec.onBidBillable(bid); + expect(server.requests[0].url).to.equals('http://win.example.com/3.4'); + }); + + it('should not fire anything when neither burl nor nurl is available', function () { + const bid = { + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250 + } + spec.onBidBillable(bid); + expect(server.requests.length).to.equal(0); + }); }); describe('onTimeout', function () { @@ -202,9 +285,13 @@ describe('Taboola Adapter', function () { 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, 'bidfloorcur': 'USD', - 'ext': {} + 'ext': { + 'prebid': { + 'bidId': defaultBidRequest.bidId + } + } }], - 'device': {'ua': navigator.userAgent}, + 'device': res.data.device, 'id': 'mock-uuid', 'test': 0, 'user': { @@ -225,15 +312,12 @@ describe('Taboola Adapter', function () { 'bcat': [], 'badv': [], 'wlang': [], - 'ext': { - 'prebid': { - 'version': '$prebid.version$' - } - } + 'ext': res.data.ext }; expect(res.url).to.equal(`${END_POINT_URL}?publisher=${commonBidRequest.params.publisherId}`); expect(JSON.stringify(res.data)).to.deep.equal(JSON.stringify(expectedData)); + expect(res.data.ext.prebid.version).to.equal('$prebid.version$'); }); it('should pass optional parameters in request', function () { @@ -392,7 +476,12 @@ describe('Taboola Adapter', function () { expect(res.data.badv).to.deep.equal(bidderRequest.ortb2.badv) expect(res.data.wlang).to.deep.equal(bidderRequest.ortb2.wlang) expect(res.data.user.id).to.deep.equal(bidderRequest.ortb2.user.id) - expect(res.data.device).to.deep.equal(bidderRequest.ortb2.device); + // Device should contain ortb2 device properties plus fraud prevention signals + expect(res.data.device.w).to.equal(bidderRequest.ortb2.device.w); + expect(res.data.device.h).to.equal(bidderRequest.ortb2.device.h); + expect(res.data.device.ua).to.equal(bidderRequest.ortb2.device.ua); + expect(res.data.device.ext.bot).to.exist; + expect(res.data.device.ext.visibility).to.exist; }); it('should pass user entities', function () { @@ -1523,5 +1612,372 @@ describe('Taboola Adapter', function () { expect(internal.getReferrer(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.ref); }); }); + + describe('detectBot', function () { + it('should return detected false for normal browsers', function () { + const result = detectBot(); + expect(result).to.have.property('detected'); + expect(result.detected).to.be.a('boolean'); + }); + + it('should return object with detected property', function () { + const result = detectBot(); + expect(result).to.be.an('object'); + expect(result).to.have.property('detected'); + }); + }); + + describe('getPageVisibility', function () { + it('should return visibility state object', function () { + const result = getPageVisibility(); + expect(result).to.be.an('object'); + expect(result).to.have.property('hidden'); + expect(result).to.have.property('state'); + expect(result).to.have.property('hasFocus'); + }); + + it('should return boolean for hidden property', function () { + const result = getPageVisibility(); + expect(result.hidden).to.be.a('boolean'); + }); + + it('should return string for state property', function () { + const result = getPageVisibility(); + expect(result.state).to.be.a('string'); + }); + + it('should return boolean for hasFocus property', function () { + const result = getPageVisibility(); + expect(result.hasFocus).to.be.a('boolean'); + }); + }); + + describe('fraud signals in buildRequests', function () { + const defaultBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'test-bid-id', + auctionId: 'test-auction-id', + sizes: [[300, 250]] + }; + + const commonBidderRequest = { + bidderRequestId: 'mock-uuid', + refererInfo: { + page: 'https://example.com/ref', + ref: 'https://ref', + domain: 'example.com', + } + }; + + it('should include bot detection in device.ext', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.device.ext.bot).to.exist; + expect(res.data.device.ext.bot).to.have.property('detected'); + }); + + it('should include visibility in device.ext', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.device.ext.visibility).to.exist; + expect(res.data.device.ext.visibility).to.have.property('hidden'); + expect(res.data.device.ext.visibility).to.have.property('state'); + expect(res.data.device.ext.visibility).to.have.property('hasFocus'); + }); + + it('should include scroll position in device.ext', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.device.ext.scroll).to.exist; + expect(res.data.device.ext.scroll).to.have.property('top'); + expect(res.data.device.ext.scroll).to.have.property('left'); + expect(res.data.device.ext.scroll.top).to.be.a('number'); + expect(res.data.device.ext.scroll.left).to.be.a('number'); + }); + + it('should include viewability in imp.ext when element exists', function () { + const adUnitCode = 'test-viewability-div'; + const testDiv = document.createElement('div'); + testDiv.id = adUnitCode; + testDiv.style.width = '300px'; + testDiv.style.height = '250px'; + document.body.appendChild(testDiv); + + const bidRequest = { + ...defaultBidRequest, + adUnitCode: adUnitCode + }; + + try { + const res = spec.buildRequests([bidRequest], commonBidderRequest); + // Viewability should be a number between 0-100 when element exists + expect(res.data.imp[0].ext.viewability).to.be.a('number'); + expect(res.data.imp[0].ext.viewability).to.be.at.least(0); + expect(res.data.imp[0].ext.viewability).to.be.at.most(100); + } finally { + document.body.removeChild(testDiv); + } + }); + + it('should not include viewability when element does not exist', function () { + const bidRequest = { + ...defaultBidRequest, + adUnitCode: 'non-existent-element-id' + }; + const res = spec.buildRequests([bidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.viewability).to.be.undefined; + }); + + it('should include placement position in imp.ext when element exists', function () { + const adUnitCode = 'test-placement-div'; + const testDiv = document.createElement('div'); + testDiv.id = adUnitCode; + testDiv.style.width = '300px'; + testDiv.style.height = '250px'; + testDiv.style.position = 'absolute'; + testDiv.style.top = '100px'; + testDiv.style.left = '50px'; + document.body.appendChild(testDiv); + + const bidRequest = { + ...defaultBidRequest, + adUnitCode: adUnitCode + }; + + try { + const res = spec.buildRequests([bidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.placement).to.exist; + expect(res.data.imp[0].ext.placement).to.have.property('top'); + expect(res.data.imp[0].ext.placement).to.have.property('left'); + expect(res.data.imp[0].ext.placement.top).to.be.a('number'); + expect(res.data.imp[0].ext.placement.left).to.be.a('number'); + } finally { + document.body.removeChild(testDiv); + } + }); + + it('should include fold detection in imp.ext when element exists', function () { + const adUnitCode = 'test-fold-div'; + const testDiv = document.createElement('div'); + testDiv.id = adUnitCode; + testDiv.style.width = '300px'; + testDiv.style.height = '250px'; + document.body.appendChild(testDiv); + + const bidRequest = { + ...defaultBidRequest, + adUnitCode: adUnitCode + }; + + try { + const res = spec.buildRequests([bidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.fold).to.exist; + expect(res.data.imp[0].ext.fold).to.be.oneOf(['above', 'below']); + } finally { + document.body.removeChild(testDiv); + } + }); + + it('should not include placement or fold when element does not exist', function () { + const bidRequest = { + ...defaultBidRequest, + adUnitCode: 'non-existent-placement-element' + }; + const res = spec.buildRequests([bidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.placement).to.be.undefined; + expect(res.data.imp[0].ext.fold).to.be.undefined; + }); + + it('should preserve existing ortb2 device ext properties', function () { + const bidderRequestWithDeviceExt = { + ...commonBidderRequest, + ortb2: { + device: { + ua: 'test-ua', + ext: { + existingProp: 'existingValue' + } + } + } + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequestWithDeviceExt); + expect(res.data.device.ext.existingProp).to.equal('existingValue'); + expect(res.data.device.ext.bot).to.exist; + expect(res.data.device.ext.visibility).to.exist; + }); + + it('should include device.js = 1', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.device.js).to.equal(1); + }); + + it('should include connectiontype when available', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + // connectiontype is optional - depends on navigator.connection availability + if (res.data.device.connectiontype !== undefined) { + expect(res.data.device.connectiontype).to.be.a('number'); + expect(res.data.device.connectiontype).to.be.oneOf([0, 1, 2, 3, 4, 5, 6, 7]); + } + }); + + it('should not override existing ortb2 device properties', function () { + const bidderRequestWithDevice = { + ...commonBidderRequest, + ortb2: { + device: { + ua: 'custom-ua', + w: 1920, + h: 1080 + } + } + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequestWithDevice); + expect(res.data.device.ua).to.equal('custom-ua'); + expect(res.data.device.w).to.equal(1920); + expect(res.data.device.h).to.equal(1080); + expect(res.data.device.js).to.equal(1); + }); + }); + + describe('Prebid IDs in buildRequests', function () { + const defaultBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'test-bid-id-123', + auctionId: 'test-auction-id-456', + adUnitCode: 'test-ad-unit-code', + sizes: [[300, 250]] + }; + + const commonBidderRequest = { + bidderRequestId: 'mock-uuid', + auctionId: 'auction-id-789', + refererInfo: { + page: 'https://example.com/ref', + ref: 'https://ref', + domain: 'example.com', + } + }; + + it('should include auctionId in ext.prebid', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.ext.prebid.auctionId).to.equal('auction-id-789'); + }); + + it('should include bidId in imp.ext.prebid', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.bidId).to.equal('test-bid-id-123'); + }); + + it('should include adUnitCode in imp.ext.prebid', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.adUnitCode).to.equal('test-ad-unit-code'); + }); + + it('should include adUnitId in imp.ext.prebid when available', function () { + const bidRequestWithAdUnitId = { + ...defaultBidRequest, + adUnitId: 'test-ad-unit-id' + }; + const res = spec.buildRequests([bidRequestWithAdUnitId], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.adUnitId).to.equal('test-ad-unit-id'); + }); + + it('should not include adUnitId when not available', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.adUnitId).to.be.undefined; + }); + + it('should include all Prebid IDs for multiple impressions', function () { + const bidRequest1 = { + ...defaultBidRequest, + bidId: 'bid-id-1', + adUnitCode: 'ad-unit-1' + }; + const bidRequest2 = { + ...defaultBidRequest, + bidId: 'bid-id-2', + adUnitCode: 'ad-unit-2' + }; + const res = spec.buildRequests([bidRequest1, bidRequest2], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.bidId).to.equal('bid-id-1'); + expect(res.data.imp[0].ext.prebid.adUnitCode).to.equal('ad-unit-1'); + expect(res.data.imp[1].ext.prebid.bidId).to.equal('bid-id-2'); + expect(res.data.imp[1].ext.prebid.adUnitCode).to.equal('ad-unit-2'); + }); + }); + + describe('Prebid counters in buildRequests', function () { + const defaultBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'test-bid-id', + adUnitCode: 'test-ad-unit', + bidRequestsCount: 3, + bidderRequestsCount: 2, + bidderWinsCount: 1, + sizes: [[300, 250]] + }; + + const commonBidderRequest = { + bidderRequestId: 'mock-uuid', + auctionId: 'test-auction-id', + refererInfo: { + page: 'https://example.com/ref', + ref: 'https://ref', + domain: 'example.com', + } + }; + + it('should include bidRequestsCount in imp.ext.prebid', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.bidRequestsCount).to.equal(3); + }); + + it('should include bidderRequestsCount in imp.ext.prebid', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.bidderRequestsCount).to.equal(2); + }); + + it('should include bidderWinsCount in imp.ext.prebid', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.prebid.bidderWinsCount).to.equal(1); + }); + + it('should include all Prebid counters for multiple impressions', function () { + const bidRequest1 = { + ...defaultBidRequest, + bidId: 'bid-id-1', + adUnitCode: 'ad-unit-1', + bidRequestsCount: 5, + bidderRequestsCount: 4, + bidderWinsCount: 2 + }; + const bidRequest2 = { + ...defaultBidRequest, + bidId: 'bid-id-2', + adUnitCode: 'ad-unit-2', + bidRequestsCount: 2, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + const res = spec.buildRequests([bidRequest1, bidRequest2], commonBidderRequest); + + expect(res.data.imp[0].ext.prebid.bidRequestsCount).to.equal(5); + expect(res.data.imp[0].ext.prebid.bidderRequestsCount).to.equal(4); + expect(res.data.imp[0].ext.prebid.bidderWinsCount).to.equal(2); + + expect(res.data.imp[1].ext.prebid.bidRequestsCount).to.equal(2); + expect(res.data.imp[1].ext.prebid.bidderRequestsCount).to.equal(1); + expect(res.data.imp[1].ext.prebid.bidderWinsCount).to.equal(0); + }); + }); }) }) diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index 838fd50f76d..9b91e71c7d8 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -7,6 +7,13 @@ describe('TargetVideo Bid Adapter', function() { const params = { placementId: 12345, }; + const videoMediaTypes = { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } + } const defaultBidderRequest = { bidderRequestId: 'mock-uuid', @@ -25,13 +32,7 @@ describe('TargetVideo Bid Adapter', function() { const videoRequest = [{ bidder, params, - mediaTypes: { - video: { - playerSize: [[640, 360]], - context: 'instream', - playbackmethod: [1, 2, 3, 4] - } - } + mediaTypes: videoMediaTypes, }]; it('Test the bid validation function', function() { @@ -353,4 +354,43 @@ describe('TargetVideo Bid Adapter', function() { const userSyncs = spec.getUserSyncs({iframeEnabled: false}); expect(userSyncs).to.have.lengthOf(0); }); + + it('Test the VIDEO request floor param', function() { + const requests = [ + { + bidder, + params: { + ...params, + floor: 2.12, + }, + mediaTypes: videoMediaTypes, + }, + { + bidder, + params: { + ...params, + floor: "1.55", + }, + mediaTypes: videoMediaTypes, + }, + { + bidder, + params: { + ...params, + floor: "abc", + }, + mediaTypes: videoMediaTypes, + } + ] + + const bids = spec.buildRequests(requests, defaultBidderRequest) + + const payload1 = JSON.parse(bids[0].data); + const payload2 = JSON.parse(bids[1].data); + const payload3 = JSON.parse(bids[2].data); + + expect(payload1.imp[0].bidfloor).to.exist.and.equal(2.12); + expect(payload2.imp[0].bidfloor).to.exist.and.equal(1.55); + expect(payload3.imp[0].bidfloor).to.not.exist; + }); }); diff --git a/test/spec/modules/tcfControl_spec.js b/test/spec/modules/tcfControl_spec.js index b8164b86eae..0794effaa70 100644 --- a/test/spec/modules/tcfControl_spec.js +++ b/test/spec/modules/tcfControl_spec.js @@ -404,6 +404,48 @@ describe('gdpr enforcement', function () { expectAllow(allowed, fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder))); }) }); + + it('should allow S2S bidder when deferS2Sbidders is true', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [], + deferS2Sbidders: true + }] + } + }); + const consent = setupConsentData(); + consent.vendorData.vendor.consents = {}; + consent.vendorData.vendor.legitimateInterests = {}; + consent.vendorData.purpose.consents['2'] = true; + + const s2sBidderParams = activityParams(MODULE_TYPE_BIDDER, 's2sBidder', {isS2S: true}); + expectAllow(true, fetchBidsRule(s2sBidderParams)); + }); + + it('should not make exceptions for client bidders when deferS2Sbidders is true', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [], + deferS2Sbidders: true + }] + } + }); + const consent = setupConsentData(); + consent.vendorData.vendor.consents = {}; + consent.vendorData.vendor.legitimateInterests = {}; + consent.vendorData.purpose.consents['2'] = true; + + const clientBidderParams = activityParams(MODULE_TYPE_BIDDER, 'clientBidder'); + expectAllow(false, fetchBidsRule(clientBidderParams)); + }); }); describe('reportAnalyticsRule', () => { @@ -880,7 +922,8 @@ describe('gdpr enforcement', function () { purpose: 'basicAds', enforcePurpose: true, enforceVendor: true, - vendorExceptions: [] + vendorExceptions: [], + deferS2Sbidders: false }]; beforeEach(function () { sandbox = sinon.createSandbox(); @@ -926,7 +969,8 @@ describe('gdpr enforcement', function () { purpose: 'basicAds', enforcePurpose: false, enforceVendor: true, - vendorExceptions: ['bidderA'] + vendorExceptions: ['bidderA'], + deferS2Sbidders: false } setEnforcementConfig({ gdpr: { diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index cec0853c114..c698473260f 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -3,6 +3,7 @@ import * as autoplay from 'libraries/autoplayDetection/autoplay.js'; import { spec, storage } from 'modules/teadsBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { getScreenOrientation } from 'src/utils.js'; +import {getDevicePixelRatio} from '../../../libraries/devicePixelRatio/devicePixelRatio.js'; const ENDPOINT = 'https://a.teads.tv/hb/bid-request'; const AD_SCRIPT = '"'; @@ -360,7 +361,7 @@ describe('teadsBidAdapter', () => { it('should add pixelRatio info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); - const pixelRatio = window.top.devicePixelRatio + const pixelRatio = getDevicePixelRatio(); expect(payload.devicePixelRatio).to.exist; expect(payload.devicePixelRatio).to.deep.equal(pixelRatio); @@ -438,28 +439,6 @@ describe('teadsBidAdapter', () => { expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); }); - it('should add hardwareConcurrency info to payload', function () { - const request = spec.buildRequests(bidRequests, bidderRequestDefault); - const payload = JSON.parse(request.data); - const hardwareConcurrency = window.top.navigator?.hardwareConcurrency - - if (hardwareConcurrency) { - expect(payload.hardwareConcurrency).to.exist; - expect(payload.hardwareConcurrency).to.deep.equal(hardwareConcurrency); - } else expect(payload.hardwareConcurrency).to.not.exist - }); - - it('should add deviceMemory info to payload', function () { - const request = spec.buildRequests(bidRequests, bidderRequestDefault); - const payload = JSON.parse(request.data); - const deviceMemory = window.top.navigator.deviceMemory - - if (deviceMemory) { - expect(payload.deviceMemory).to.exist; - expect(payload.deviceMemory).to.deep.equal(deviceMemory); - } else expect(payload.deviceMemory).to.not.exist; - }); - describe('pageTitle', function () { it('should add pageTitle info to payload based on document title', function () { const testText = 'This is a title'; diff --git a/test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js b/test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js new file mode 100644 index 00000000000..f2dbe70f30d --- /dev/null +++ b/test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js @@ -0,0 +1,440 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/teqBlazeSalesAgentBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'teqBlazeSalesAgent'; + +describe('TeqBlazeSalesAgentBidAdapter', 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', + }, + site: { + ext: { + data: { + scope3_aee: { + include: 'include', + exclude: 'exclude', + macro: 'macro' + } + } + } + } + }, + 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 () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + 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); + expect(placement.axei).to.exist.and.to.be.equal('include'); + expect(placement.axex).to.exist.and.to.be.equal('exclude'); + expect(placement.axem).to.exist.and.to.be.equal('macro'); + + 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); + const 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); + const 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] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const 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]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + 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 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const 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 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const 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 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const 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' + }] + }; + + const 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' + }] + }; + const 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', + }] + }; + const 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' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/terceptAnalyticsAdapter_spec.js b/test/spec/modules/terceptAnalyticsAdapter_spec.js index bcbfaf63ae8..45184941b2d 100644 --- a/test/spec/modules/terceptAnalyticsAdapter_spec.js +++ b/test/spec/modules/terceptAnalyticsAdapter_spec.js @@ -42,14 +42,8 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, @@ -65,21 +59,23 @@ describe('tercept analytics adapter', function () { } ], 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98' + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'adslot': '/1234567/homepage-banner' + }, + 'pbadslot': 'homepage-banner-pbadslot' + } + } + } } ], - 'adUnitCodes': [ - 'div-gpt-ad-1460505748561-0' - ], + 'adUnitCodes': ['div-gpt-ad-1460505748561-0'], 'bidderRequests': [ { 'bidderCode': 'appnexus', @@ -97,28 +93,16 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], 'bidId': '263efc09896d0c', 'bidderRequestId': '155975c76e13b1', @@ -135,9 +119,7 @@ describe('tercept analytics adapter', function () { 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', 'reachedTop': true, 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, 'start': 1576823893838 }, @@ -157,28 +139,16 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], 'bidId': '9424dea605368f', 'bidderRequestId': '181df4d465699c', @@ -195,9 +165,7 @@ describe('tercept analytics adapter', function () { 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', 'reachedTop': true, 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, 'start': 1576823893838 } @@ -223,28 +191,16 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], 'bidId': '263efc09896d0c', 'bidderRequestId': '155975c76e13b1', @@ -261,9 +217,7 @@ describe('tercept analytics adapter', function () { 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', 'reachedTop': true, 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, 'start': 1576823893838 }, @@ -283,28 +237,16 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'd99d90e0-663a-459d-8c87-4c92ce6a527c', 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], 'bidId': '9424dea605368f', 'bidderRequestId': '181df4d465699c', @@ -321,9 +263,7 @@ describe('tercept analytics adapter', function () { 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', 'reachedTop': true, 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, 'start': 1576823893838 }, @@ -362,14 +302,8 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, @@ -378,14 +312,13 @@ describe('tercept analytics adapter', function () { 'sizes': [[300, 250]], 'bidId': '9424dea605368f', 'bidderRequestId': '181df4d465699c', - 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', 'src': 's2s', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0 }, - 'bidTimeout': [ - ], + 'bidTimeout': [], 'bidResponse': { 'bidderCode': 'appnexus', 'width': 300, @@ -442,14 +375,8 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, @@ -465,21 +392,23 @@ describe('tercept analytics adapter', function () { } ], 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98' + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'adslot': '/1234567/homepage-banner' + }, + 'pbadslot': 'homepage-banner-pbadslot' + } + } + } } ], - 'adUnitCodes': [ - 'div-gpt-ad-1460505748561-0' - ], + 'adUnitCodes': ['div-gpt-ad-1460505748561-0'], 'bidderRequests': [ { 'bidderCode': 'appnexus', @@ -497,28 +426,16 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], 'bidId': '263efc09896d0c', 'bidderRequestId': '155975c76e13b1', @@ -535,9 +452,7 @@ describe('tercept analytics adapter', function () { 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', 'reachedTop': true, 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, 'start': 1576823893838 } @@ -625,28 +540,16 @@ describe('tercept analytics adapter', function () { 'mediaTypes': { 'banner': { 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ] } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + [300, 250], + [300, 600] ], 'bidId': '263efc09896d0c', 'bidderRequestId': '155975c76e13b1', @@ -663,9 +566,7 @@ describe('tercept analytics adapter', function () { 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', 'reachedTop': true, 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, 'start': 1576823893838 }, @@ -721,222 +622,228 @@ describe('tercept analytics adapter', function () { ] } }; + const location = utils.getWindowLocation(); const expectedAfterBid = { - "bids": [ + 'bids': [ { - "bidderCode": "appnexus", - "bidId": "263efc09896d0c", - "adUnitCode": "div-gpt-ad-1460505748561-0", - "requestId": "155975c76e13b1", - "auctionId": "db377024-d866-4a24-98ac-5e430f881313", - "sizes": "300x250,300x600", - "renderStatus": 2, - "requestTimestamp": 1576823893838, - "creativeId": 96846035, - "currency": "USD", - "cpm": 0.5, - "netRevenue": true, - "mediaType": "banner", - "statusMessage": "Bid available", - "timeToRespond": 212, - "responseTimestamp": 1576823894050 + 'bidderCode': 'appnexus', + 'bidId': '263efc09896d0c', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'requestId': '155975c76e13b1', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'sizes': '300x250,300x600', + 'renderStatus': 2, + 'requestTimestamp': 1576823893838, + 'creativeId': 96846035, + 'currency': 'USD', + 'cpm': 0.5, + 'netRevenue': true, + 'renderedSize': null, + 'width': 300, + 'height': 250, + 'mediaType': 'banner', + 'statusMessage': 'Bid available', + 'timeToRespond': 212, + 'responseTimestamp': 1576823894050, + 'renderTimestamp': null, + 'reason': null, + 'message': null, + 'host': null, + 'path': null, + 'search': null, + 'adserverAdSlot': '/1234567/homepage-banner', + 'pbAdSlot': 'homepage-banner-pbadslot', + 'ttl': 300, + 'ad': '', + 'adId': '393976d8770041', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '393976d8770041', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'meta': { + 'advertiserId': 2529885 + } }, { - "bidderCode": "ix", - "adUnitCode": "div-gpt-ad-1460505748561-0", - "requestId": "181df4d465699c", - "auctionId": "86e005fa-1900-4782-b6df-528500f09128", - "transactionId": "d99d90e0-663a-459d-8c87-4c92ce6a527c", - "sizes": "300x250,300x600", - "renderStatus": 5, - "responseTimestamp": 1753444800000 + 'bidderCode': 'ix', + 'bidId': '9424dea605368f', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'requestId': '181df4d465699c', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'transactionId': 'd99d90e0-663a-459d-8c87-4c92ce6a527c', + 'sizes': '300x250,300x600', + 'renderStatus': 5, + 'renderedSize': null, + 'renderTimestamp': null, + 'reason': null, + 'message': null, + 'host': null, + 'path': null, + 'search': null, + 'responseTimestamp': 1753444800000, + 'adserverAdSlot': '/1234567/homepage-banner', + 'pbAdSlot': 'homepage-banner-pbadslot', + 'meta': {} } ], - "auctionInit": { - "auctionId": "db377024-d866-4a24-98ac-5e430f881313", - "timestamp": 1576823893836, - "auctionStatus": "inProgress", - "adUnits": [ + 'auctionInit': { + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'timestamp': 1576823893836, + 'auctionStatus': 'inProgress', + 'adUnits': [ { - "code": "div-gpt-ad-1460505748561-0", - "mediaTypes": { - "banner": { - "sizes": [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'code': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] ] } }, - "bids": [ + 'bids': [ { - "bidder": "appnexus", - "params": { - "placementId": 13144370 + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 }, - "crumbs": { - "pubcid": "ff4002c4-ce05-4a61-b4ef-45a3cd93991a" + 'crumbs': { + 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' } } ], - "sizes": [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'sizes': [ + [300, 250], + [300, 600] ], - "transactionId": "6d275806-1943-4f3e-9cd5-624cbd05ad98" + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'adslot': '/1234567/homepage-banner' + }, + 'pbadslot': 'homepage-banner-pbadslot' + } + } + } } ], - "adUnitCodes": [ - "div-gpt-ad-1460505748561-0" - ], - "bidderRequests": [ + 'adUnitCodes': ['div-gpt-ad-1460505748561-0'], + 'bidderRequests': [ { - "bidderCode": "appnexus", - "auctionId": "db377024-d866-4a24-98ac-5e430f881313", - "bidderRequestId": "155975c76e13b1", - "bids": [ + 'bidderCode': 'appnexus', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'bidderRequestId': '155975c76e13b1', + 'bids': [ { - "bidder": "appnexus", - "params": { - "placementId": 13144370 + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 }, - "crumbs": { - "pubcid": "ff4002c4-ce05-4a61-b4ef-45a3cd93991a" + 'crumbs': { + 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' }, - "mediaTypes": { - "banner": { - "sizes": [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] ] } }, - "adUnitCode": "div-gpt-ad-1460505748561-0", - "transactionId": "6d275806-1943-4f3e-9cd5-624cbd05ad98", - "sizes": [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'sizes': [ + [300, 250], + [300, 600] ], - "bidId": "263efc09896d0c", - "bidderRequestId": "155975c76e13b1", - "auctionId": "db377024-d866-4a24-98ac-5e430f881313", - "src": "client", - "bidRequestsCount": 1, - "bidderRequestsCount": 1, - "bidderWinsCount": 0 + 'bidId': '263efc09896d0c', + 'bidderRequestId': '155975c76e13b1', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 } ], - "auctionStart": 1576823893836, - "timeout": 1000, - "refererInfo": { - "referer": "http://observer.com/integrationExamples/gpt/hello_world.html", - "reachedTop": true, - "numIframes": 0, - "stack": [ - "http://observer.com/integrationExamples/gpt/hello_world.html" - ] + 'auctionStart': 1576823893836, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', + 'reachedTop': true, + 'numIframes': 0, + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, - "start": 1576823893838 + 'start': 1576823893838 }, { - "bidderCode": "ix", - "auctionId": "db377024-d866-4a24-98ac-5e430f881313", - "bidderRequestId": "181df4d465699c", - "bids": [ + 'bidderCode': 'ix', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'bidderRequestId': '181df4d465699c', + 'bids': [ { - "bidder": "ix", - "params": { - "placementId": 13144370 + 'bidder': 'ix', + 'params': { + 'placementId': 13144370 }, - "crumbs": { - "pubcid": "ff4002c4-ce05-4a61-b4ef-45a3cd93991a" + 'crumbs': { + 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' }, - "mediaTypes": { - "banner": { - "sizes": [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] ] } }, - "adUnitCode": "div-gpt-ad-1460505748561-0", - "transactionId": "6d275806-1943-4f3e-9cd5-624cbd05ad98", - "sizes": [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'sizes': [ + [300, 250], + [300, 600] ], - "bidId": "9424dea605368f", - "bidderRequestId": "181df4d465699c", - "auctionId": "db377024-d866-4a24-98ac-5e430f881313", - "src": "client", - "bidRequestsCount": 1, - "bidderRequestsCount": 1, - "bidderWinsCount": 0 + 'bidId': '9424dea605368f', + 'bidderRequestId': '181df4d465699c', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 } ], - "auctionStart": 1576823893836, - "timeout": 1000, - "refererInfo": { - "referer": "http://observer.com/integrationExamples/gpt/hello_world.html", - "reachedTop": true, - "numIframes": 0, - "stack": [ - "http://observer.com/integrationExamples/gpt/hello_world.html" - ] + 'auctionStart': 1576823893836, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', + 'reachedTop': true, + 'numIframes': 0, + 'stack': ['http://observer.com/integrationExamples/gpt/hello_world.html'] }, - "start": 1576823893838 + 'start': 1576823893838 } ], - "noBids": [], - "bidsReceived": [], - "winningBids": [], - "timeout": 1000, - "host": "localhost:9876", - "path": "/context.html", - "search": "" + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000, + 'host': 'localhost:9876', + 'path': '/context.html', + 'search': '' }, - "initOptions": { - "pubId": "1", - "pubKey": "ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==", - "hostName": "us-central1-quikr-ebay.cloudfunctions.net", - "pathName": "/prebid-analytics" + 'initOptions': { + 'pubId': '1', + 'pubKey': 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + 'hostName': 'us-central1-quikr-ebay.cloudfunctions.net', + 'pathName': '/prebid-analytics' } }; @@ -951,6 +858,8 @@ describe('tercept analytics adapter', function () { 'cpm': 0.5, 'netRevenue': true, 'renderedSize': '300x250', + 'width': 300, + 'height': 250, 'mediaType': 'banner', 'statusMessage': 'Bid available', 'status': 'rendered', @@ -958,12 +867,31 @@ describe('tercept analytics adapter', function () { 'timeToRespond': 212, 'requestTimestamp': 1576823893838, 'responseTimestamp': 1576823894050, - "host": "localhost", - "path": "/context.html", - "search": "", + 'renderTimestamp': null, + 'reason': null, + 'message': null, + 'host': 'localhost', + 'path': '/context.html', + 'search': '', + 'adserverAdSlot': '/1234567/homepage-banner', + 'pbAdSlot': 'homepage-banner-pbadslot', + 'ttl': 300, + 'ad': '', + 'adId': '393976d8770041', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '393976d8770041', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'meta': { + 'advertiserId': 2529885 + } }, 'initOptions': initOptions - } + }; adapterManager.registerAnalyticsAdapter({ code: 'tercept', @@ -1002,19 +930,351 @@ describe('tercept analytics adapter', function () { events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); expect(server.requests.length).to.equal(1); - const realAfterBid = JSON.parse(server.requests[0].requestBody); - expect(realAfterBid).to.deep.equal(expectedAfterBid); // Step 7: Send auction bid won event events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); expect(server.requests.length).to.equal(2); - const winEventData = JSON.parse(server.requests[1].requestBody); - expect(winEventData).to.deep.equal(expectedAfterBidWon); }); + + it('uses correct adUnits for each auction via Map lookup', function () { + const auction1Init = { + 'auctionId': 'auction-1-id', + 'timestamp': 1576823893836, + 'auctionStatus': 'inProgress', + 'adUnits': [ + { + 'code': 'div-auction-1', + 'mediaTypes': { 'banner': { 'sizes': [[300, 250]] } }, + 'bids': [{ 'bidder': 'appnexus', 'params': { 'placementId': 111 } }], + 'sizes': [[300, 250]], + 'transactionId': 'trans-1', + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { 'adslot': '/auction1/slot' }, + 'pbadslot': 'auction1-pbadslot' + } + } + } + } + ], + 'adUnitCodes': ['div-auction-1'], + 'bidderRequests': [], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000 + }; + + const auction2Init = { + 'auctionId': 'auction-2-id', + 'timestamp': 1576823893900, + 'auctionStatus': 'inProgress', + 'adUnits': [ + { + 'code': 'div-auction-2', + 'mediaTypes': { 'banner': { 'sizes': [[728, 90]] } }, + 'bids': [{ 'bidder': 'rubicon', 'params': { 'placementId': 222 } }], + 'sizes': [[728, 90]], + 'transactionId': 'trans-2', + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { 'adslot': '/auction2/slot' }, + 'pbadslot': 'auction2-pbadslot' + } + } + } + } + ], + 'adUnitCodes': ['div-auction-2'], + 'bidderRequests': [], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000 + }; + + events.emit(EVENTS.AUCTION_INIT, auction1Init); + events.emit(EVENTS.AUCTION_INIT, auction2Init); + + const bidWon1 = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'adId': 'ad-1', + 'requestId': 'bid-1', + 'mediaType': 'banner', + 'cpm': 1.0, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-auction-1', + 'auctionId': 'auction-1-id', + 'responseTimestamp': 1576823894000, + 'requestTimestamp': 1576823893838, + 'bidder': 'appnexus', + 'timeToRespond': 164, + 'size': '300x250', + 'status': 'rendered', + 'meta': {} + }; + + const bidWon2 = { + 'bidderCode': 'rubicon', + 'width': 728, + 'height': 90, + 'adId': 'ad-2', + 'requestId': 'bid-2', + 'mediaType': 'banner', + 'cpm': 2.0, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-auction-2', + 'auctionId': 'auction-2-id', + 'responseTimestamp': 1576823894100, + 'requestTimestamp': 1576823893900, + 'bidder': 'rubicon', + 'timeToRespond': 200, + 'size': '728x90', + 'status': 'rendered', + 'meta': {} + }; + + events.emit(EVENTS.BID_WON, bidWon1); + events.emit(EVENTS.BID_WON, bidWon2); + + expect(server.requests.length).to.equal(2); + + const winData1 = JSON.parse(server.requests[0].requestBody); + expect(winData1.bidWon.adserverAdSlot).to.equal('/auction1/slot'); + expect(winData1.bidWon.pbAdSlot).to.equal('auction1-pbadslot'); + + const winData2 = JSON.parse(server.requests[1].requestBody); + expect(winData2.bidWon.adserverAdSlot).to.equal('/auction2/slot'); + expect(winData2.bidWon.pbAdSlot).to.equal('auction2-pbadslot'); + }); + + it('handles BIDDER_ERROR event', function () { + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + const bidderError = { + 'bidderCode': 'appnexus', + 'error': 'timeout', + 'bidderRequest': { + 'bidderCode': 'appnexus', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313' + }; + + events.emit(EVENTS.BIDDER_ERROR, bidderError); + + expect(server.requests.length).to.equal(1); + const errorData = JSON.parse(server.requests[0].requestBody); + expect(errorData.bidderError).to.exist; + expect(errorData.bidderError.status).to.equal(6); + expect(errorData.bidderError.adserverAdSlot).to.equal('/1234567/homepage-banner'); + expect(errorData.bidderError.pbAdSlot).to.equal('homepage-banner-pbadslot'); + expect(errorData.bidderError.host).to.equal(window.location.hostname); + expect(errorData.bidderError.path).to.equal(window.location.pathname); + }); + + it('returns empty object for getAdSlotData when ad unit not found', function () { + const auctionInitNoOrtb2 = { + 'auctionId': 'no-ortb2-auction', + 'timestamp': 1576823893836, + 'auctionStatus': 'inProgress', + 'adUnits': [ + { + 'code': 'div-no-ortb2', + 'mediaTypes': { 'banner': { 'sizes': [[300, 250]] } }, + 'bids': [{ 'bidder': 'appnexus', 'params': { 'placementId': 999 } }], + 'sizes': [[300, 250]], + 'transactionId': 'trans-no-ortb2' + } + ], + 'adUnitCodes': ['div-no-ortb2'], + 'bidderRequests': [], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000 + }; + + const bidRequest = { + 'bidderCode': 'appnexus', + 'auctionId': 'no-ortb2-auction', + 'bidderRequestId': 'req-no-ortb2', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { 'placementId': 999 }, + 'mediaTypes': { 'banner': { 'sizes': [[300, 250]] } }, + 'adUnitCode': 'div-no-ortb2', + 'transactionId': 'trans-no-ortb2', + 'sizes': [[300, 250]], + 'bidId': 'bid-no-ortb2', + 'bidderRequestId': 'req-no-ortb2', + 'auctionId': 'no-ortb2-auction' + }], + 'auctionStart': 1576823893836 + }; + + const bidResponse = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'adId': 'ad-no-ortb2', + 'requestId': 'bid-no-ortb2', + 'mediaType': 'banner', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-no-ortb2', + 'auctionId': 'no-ortb2-auction', + 'responseTimestamp': 1576823894000, + 'bidder': 'appnexus', + 'timeToRespond': 164, + 'meta': {} + }; + + events.emit(EVENTS.AUCTION_INIT, auctionInitNoOrtb2); + events.emit(EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.AUCTION_END, { auctionId: 'no-ortb2-auction' }); + + expect(server.requests.length).to.equal(1); + const auctionData = JSON.parse(server.requests[0].requestBody); + const bid = auctionData.bids.find(b => b.bidId === 'bid-no-ortb2'); + expect(bid.adserverAdSlot).to.be.undefined; + expect(bid.pbAdSlot).to.be.undefined; + }); + + it('handles AD_RENDER_SUCCEEDED event', function () { + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + const adRenderSucceeded = { + 'bid': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '393976d8770041', + 'requestId': '263efc09896d0c', + 'mediaType': 'banner', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'responseTimestamp': 1576823894050, + 'requestTimestamp': 1576823893838, + 'bidder': 'appnexus', + 'timeToRespond': 212, + 'size': '300x250', + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'meta': { + 'advertiserId': 2529885 + } + }, + 'doc': {}, + 'adId': '393976d8770041' + }; + + events.emit(EVENTS.AD_RENDER_SUCCEEDED, adRenderSucceeded); + + expect(server.requests.length).to.equal(1); + const renderData = JSON.parse(server.requests[0].requestBody); + expect(renderData.adRenderSucceeded).to.exist; + expect(renderData.adRenderSucceeded.renderStatus).to.equal(7); + expect(renderData.adRenderSucceeded.renderTimestamp).to.be.a('number'); + expect(renderData.adRenderSucceeded.bidderCode).to.equal('appnexus'); + expect(renderData.adRenderSucceeded.bidId).to.equal('263efc09896d0c'); + expect(renderData.adRenderSucceeded.adUnitCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(renderData.adRenderSucceeded.auctionId).to.equal('db377024-d866-4a24-98ac-5e430f881313'); + expect(renderData.adRenderSucceeded.cpm).to.equal(0.5); + expect(renderData.adRenderSucceeded.renderedSize).to.equal('300x250'); + expect(renderData.adRenderSucceeded.adserverAdSlot).to.equal('/1234567/homepage-banner'); + expect(renderData.adRenderSucceeded.pbAdSlot).to.equal('homepage-banner-pbadslot'); + expect(renderData.adRenderSucceeded.host).to.equal(window.location.hostname); + expect(renderData.adRenderSucceeded.path).to.equal(window.location.pathname); + expect(renderData.adRenderSucceeded.reason).to.be.null; + expect(renderData.adRenderSucceeded.message).to.be.null; + }); + + it('handles AD_RENDER_FAILED event', function () { + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + const adRenderFailed = { + 'bid': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '393976d8770041', + 'requestId': '263efc09896d0c', + 'mediaType': 'banner', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'responseTimestamp': 1576823894050, + 'requestTimestamp': 1576823893838, + 'bidder': 'appnexus', + 'timeToRespond': 212, + 'size': '300x250', + 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', + 'meta': { + 'advertiserId': 2529885 + } + }, + 'adId': '393976d8770041', + 'reason': 'exception', + 'message': 'Error rendering ad: Cannot read property of undefined' + }; + + events.emit(EVENTS.AD_RENDER_FAILED, adRenderFailed); + + expect(server.requests.length).to.equal(1); + const renderData = JSON.parse(server.requests[0].requestBody); + expect(renderData.adRenderFailed).to.exist; + expect(renderData.adRenderFailed.renderStatus).to.equal(8); + expect(renderData.adRenderFailed.renderTimestamp).to.be.a('number'); + expect(renderData.adRenderFailed.bidderCode).to.equal('appnexus'); + expect(renderData.adRenderFailed.bidId).to.equal('263efc09896d0c'); + expect(renderData.adRenderFailed.adUnitCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(renderData.adRenderFailed.auctionId).to.equal('db377024-d866-4a24-98ac-5e430f881313'); + expect(renderData.adRenderFailed.cpm).to.equal(0.5); + expect(renderData.adRenderFailed.reason).to.equal('exception'); + expect(renderData.adRenderFailed.message).to.equal('Error rendering ad: Cannot read property of undefined'); + expect(renderData.adRenderFailed.adserverAdSlot).to.equal('/1234567/homepage-banner'); + expect(renderData.adRenderFailed.pbAdSlot).to.equal('homepage-banner-pbadslot'); + expect(renderData.adRenderFailed.host).to.equal(window.location.hostname); + expect(renderData.adRenderFailed.path).to.equal(window.location.pathname); + }); + + it('includes null render fields in bidWon for consistency', function () { + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); + + expect(server.requests.length).to.equal(1); + const winData = JSON.parse(server.requests[0].requestBody); + expect(winData.bidWon.renderTimestamp).to.be.null; + expect(winData.bidWon.reason).to.be.null; + expect(winData.bidWon.message).to.be.null; + }); }); }); diff --git a/test/spec/modules/theAdxBidAdapter_spec.js b/test/spec/modules/theAdxBidAdapter_spec.js index fd2a306ca05..2b531b6b10b 100644 --- a/test/spec/modules/theAdxBidAdapter_spec.js +++ b/test/spec/modules/theAdxBidAdapter_spec.js @@ -416,7 +416,7 @@ describe('TheAdxAdapter', function () { expect(result).to.eql([]); }); - it('returns a valid bid response on sucessful banner request', function () { + it('returns a valid bid response on successful banner request', function () { const incomingRequestId = 'XXtestingXX'; const responsePrice = 3.14 @@ -484,7 +484,7 @@ describe('TheAdxAdapter', function () { expect(processedBid.currency).to.equal(responseCurrency); }); - it('returns a valid deal bid response on sucessful banner request with deal', function () { + it('returns a valid deal bid response on successful banner request with deal', function () { const incomingRequestId = 'XXtestingXX'; const responsePrice = 3.14 @@ -556,7 +556,7 @@ describe('TheAdxAdapter', function () { expect(processedBid.dealId).to.equal(dealId); }); - it('returns an valid bid response on sucessful video request', function () { + it('returns an valid bid response on successful video request', function () { const incomingRequestId = 'XXtesting-275XX'; const responsePrice = 6 const vast_url = 'https://theadx.com/vast?rid=a8ae0b48-a8db-4220-ba0c-7458f452b1f5&{FOR_COVARAGE}' @@ -622,7 +622,7 @@ describe('TheAdxAdapter', function () { expect(processedBid.vastUrl).to.equal(vast_url); }); - it('returns an valid bid response on sucessful native request', function () { + it('returns an valid bid response on successful native request', function () { const incomingRequestId = 'XXtesting-275XX'; const responsePrice = 6 const nurl = 'https://app.theadx.com/ixc?rid=02aefd80-2df9-11e9-896d-d33384d77f5c&time=v-1549888312715&sp=1WzMjcRpeyk%3D'; diff --git a/test/spec/modules/timeoutRtdProvider_spec.js b/test/spec/modules/timeoutRtdProvider_spec.js index b4231c3db7c..4776c52440e 100644 --- a/test/spec/modules/timeoutRtdProvider_spec.js +++ b/test/spec/modules/timeoutRtdProvider_spec.js @@ -3,206 +3,6 @@ import { expect } from 'chai'; import * as ajax from 'src/ajax.js'; import * as prebidGlobal from 'src/prebidGlobal.js'; -const DEFAULT_USER_AGENT = window.navigator.userAgent; -const DEFAULT_CONNECTION = window.navigator.connection; - -const PC_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'; -const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'; -const TABLET_USER_AGENT = 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148'; - -function resetUserAgent() { - window.navigator.__defineGetter__('userAgent', () => DEFAULT_USER_AGENT); -}; - -function setUserAgent(userAgent) { - window.navigator.__defineGetter__('userAgent', () => userAgent); -} - -function resetConnection() { - window.navigator.__defineGetter__('connection', () => DEFAULT_CONNECTION); -} -function setConnectionType(connectionType) { - window.navigator.__defineGetter__('connection', () => { return {'type': connectionType} }); -} - -describe('getDeviceType', () => { - afterEach(() => { - resetUserAgent(); - }); - - [ - // deviceType, userAgent, deviceTypeNum - ['pc', PC_USER_AGENT, 2], - ['mobile', MOBILE_USER_AGENT, 4], - ['tablet', TABLET_USER_AGENT, 5], - ].forEach(function(args) { - const [deviceType, userAgent, deviceTypeNum] = args; - it(`should be able to recognize ${deviceType} devices`, () => { - setUserAgent(userAgent); - const res = timeoutRtdFunctions.getDeviceType(); - expect(res).to.equal(deviceTypeNum) - }) - }) -}); - -describe('getConnectionSpeed', () => { - afterEach(() => { - resetConnection(); - }); - [ - // connectionType, connectionSpeed - ['slow-2g', 'slow'], - ['2g', 'slow'], - ['3g', 'medium'], - ['bluetooth', 'fast'], - ['cellular', 'fast'], - ['ethernet', 'fast'], - ['wifi', 'fast'], - ['wimax', 'fast'], - ['4g', 'fast'], - ['not known', 'unknown'], - [undefined, 'unknown'], - ].forEach(function(args) { - const [connectionType, connectionSpeed] = args; - it(`should be able to categorize connection speed when the connection type is ${connectionType}`, () => { - setConnectionType(connectionType); - const res = timeoutRtdFunctions.getConnectionSpeed(); - expect(res).to.equal(connectionSpeed) - }) - }) -}); - -describe('Timeout modifier calculations', () => { - let sandbox; - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should be able to detect video ad units', () => { - let adUnits = [] - let res = timeoutRtdFunctions.checkVideo(adUnits); - expect(res).to.be.false; - - adUnits = [{ - mediaTypes: { - video: [] - } - }]; - res = timeoutRtdFunctions.checkVideo(adUnits); - expect(res).to.be.true; - - adUnits = [{ - mediaTypes: { - banner: [] - } - }]; - res = timeoutRtdFunctions.checkVideo(adUnits); - expect(res).to.be.false; - }); - - it('should calculate the timeout modifier for video', () => { - sandbox.stub(timeoutRtdFunctions, 'checkVideo').returns(true); - const rules = { - includesVideo: { - 'true': 200, - 'false': 50 - } - } - const res = timeoutRtdFunctions.calculateTimeoutModifier([], rules); - expect(res).to.equal(200) - }); - - it('should calculate the timeout modifier for connectionSpeed', () => { - sandbox.stub(timeoutRtdFunctions, 'getConnectionSpeed').returns('slow'); - const rules = { - connectionSpeed: { - 'slow': 200, - 'medium': 100, - 'fast': 50 - } - } - const res = timeoutRtdFunctions.calculateTimeoutModifier([], rules); - expect(res).to.equal(200); - }); - - it('should calculate the timeout modifier for deviceType', () => { - sandbox.stub(timeoutRtdFunctions, 'getDeviceType').returns(4); - const rules = { - deviceType: { - '2': 50, - '4': 100, - '5': 200 - }, - } - const res = timeoutRtdFunctions.calculateTimeoutModifier([], rules); - expect(res).to.equal(100) - }); - - it('should calculate the timeout modifier for ranged numAdunits', () => { - const rules = { - numAdUnits: { - '1-5': 100, - '6-10': 200, - '11-15': 300, - } - } - const adUnits = [1, 2, 3, 4, 5, 6]; - const res = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); - expect(res).to.equal(200) - }); - - it('should calculate the timeout modifier for exact numAdunits', () => { - const rules = { - numAdUnits: { - '1': 100, - '2': 200, - '3': 300, - '4-5': 400, - } - } - const adUnits = [1, 2]; - const res = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); - expect(res).to.equal(200); - }); - - it('should add up all the modifiers when all the rules are present', () => { - sandbox.stub(timeoutRtdFunctions, 'getConnectionSpeed').returns('slow'); - sandbox.stub(timeoutRtdFunctions, 'getDeviceType').returns(4); - const rules = { - connectionSpeed: { - 'slow': 200, - 'medium': 100, - 'fast': 50 - }, - deviceType: { - '2': 50, - '4': 100, - '5': 200 - }, - includesVideo: { - 'true': 200, - 'false': 50 - }, - numAdUnits: { - '1': 100, - '2': 200, - '3': 300, - '4-5': 400, - } - } - const res = timeoutRtdFunctions.calculateTimeoutModifier([{ - mediaTypes: { - video: [] - } - }], rules); - expect(res).to.equal(600); - }); -}); - describe('Timeout RTD submodule', () => { let sandbox; beforeEach(() => { diff --git a/test/spec/modules/topLevelPaapi_spec.js b/test/spec/modules/topLevelPaapi_spec.js index bceed8b523a..c08a14f899f 100644 --- a/test/spec/modules/topLevelPaapi_spec.js +++ b/test/spec/modules/topLevelPaapi_spec.js @@ -287,9 +287,13 @@ describe('topLevelPaapi', () => { }); }); + function getBidToRenderPm(adId, forRender = true) { + return new Promise((resolve) => getBidToRender(adId, forRender, resolve)); + } + it('should hook into getBidToRender', () => { return getBids({adUnitCode: 'au', auctionId}).then(res => { - return getBidToRender(res.au.adId).then(bidToRender => [res.au, bidToRender]) + return getBidToRenderPm(res.au.adId).then(bidToRender => [res.au, bidToRender]) }).then(([paapiBid, bidToRender]) => { if (canRender) { expect(bidToRender).to.eql(paapiBid) @@ -319,7 +323,7 @@ describe('topLevelPaapi', () => { it(`should ${!canRender ? 'NOT' : ''} override winning bid for the same adUnit`, () => { return Promise.all([ getBids({adUnitCode: 'au', auctionId}).then(res => res.au), - getBidToRender(mockContextual.adId) + getBidToRenderPm(mockContextual.adId) ]).then(([paapiBid, bidToRender]) => { if (canRender) { expect(bidToRender).to.eql(paapiBid); @@ -332,14 +336,14 @@ describe('topLevelPaapi', () => { it('should not override when the ad unit has no paapi winner', () => { mockContextual.adUnitCode = 'other'; - return getBidToRender(mockContextual.adId).then(bidToRender => { + return getBidToRenderPm(mockContextual.adId).then(bidToRender => { expect(bidToRender).to.eql(mockContextual); }) }); it('should not override when already a paapi bid', () => { return getBids({adUnitCode: 'au', auctionId}).then(res => { - return getBidToRender(res.au.adId).then((bidToRender) => [bidToRender, res.au]); + return getBidToRenderPm(res.au.adId).then((bidToRender) => [bidToRender, res.au]); }).then(([bidToRender, paapiBid]) => { expect(bidToRender).to.eql(canRender ? paapiBid : mockContextual) }) @@ -348,21 +352,21 @@ describe('topLevelPaapi', () => { if (canRender) { it('should not not override when the bid was already rendered', () => { getBids(); - return getBidToRender(mockContextual.adId).then((bid) => { + return getBidToRenderPm(mockContextual.adId).then((bid) => { // first pass - paapi wins over contextual expect(bid.source).to.eql('paapi'); bid.status = BID_STATUS.RENDERED; - return getBidToRender(mockContextual.adId, false).then(bidToRender => [bid, bidToRender]) + return getBidToRenderPm(mockContextual.adId, false).then(bidToRender => [bid, bidToRender]) }).then(([paapiBid, bidToRender]) => { // if `forRender` = false (bit retrieved for x-domain events and such) // the referenced bid is still paapi expect(bidToRender).to.eql(paapiBid); - return getBidToRender(mockContextual.adId); + return getBidToRenderPm(mockContextual.adId); }).then(bidToRender => { // second pass, paapi has been rendered, contextual should win expect(bidToRender).to.eql(mockContextual); bidToRender.status = BID_STATUS.RENDERED; - return getBidToRender(mockContextual.adId, false); + return getBidToRenderPm(mockContextual.adId, false); }).then(bidToRender => { // if the contextual bid has been rendered, it's the one being referenced expect(bidToRender).to.eql(mockContextual); diff --git a/test/spec/modules/toponBidAdapter_spec.js b/test/spec/modules/toponBidAdapter_spec.js index bf717e4b847..2922398ef16 100644 --- a/test/spec/modules/toponBidAdapter_spec.js +++ b/test/spec/modules/toponBidAdapter_spec.js @@ -1,6 +1,8 @@ import { expect } from "chai"; import { spec } from "modules/toponBidAdapter.js"; import * as utils from "src/utils.js"; +const USER_SYNC_URL = "https://pb.anyrtb.com/pb/page/prebidUserSyncs.html"; +const USER_SYNC_IMG_URL = "https://cm.anyrtb.com/cm/sdk_sync"; describe("TopOn Adapter", function () { const PREBID_VERSION = "$prebid.version$"; @@ -119,4 +121,96 @@ describe("TopOn Adapter", function () { expect(bidResponses[0].height).to.equal(250); }); }); + + describe("GetUserSyncs", function () { + it("should return correct sync URLs when iframeEnabled is true", function () { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true, + }; + + spec.buildRequests(validBidRequests, bidderRequest); + const result = spec.getUserSyncs(syncOptions, [], {}); + + expect(result).to.be.an("array"); + expect(result[0].type).to.equal("iframe"); + expect(result[0].url).to.include(USER_SYNC_URL); + expect(result[0].url).to.include("pubid=tpnpub-uuid"); + }); + + it("should return correct sync URLs when pixelEnabled is true", function () { + const syncOptions = { + iframeEnabled: false, + pixelEnabled: true, + }; + + spec.buildRequests(validBidRequests, bidderRequest); + const result = spec.getUserSyncs(syncOptions, [], {}); + + expect(result).to.be.an("array"); + expect(result[0].type).to.equal("image"); + expect(result[0].url).to.include(USER_SYNC_IMG_URL); + expect(result[0].url).to.include("pubid=tpnpub-uuid"); + }); + + it("should respect gdpr consent data", function () { + const gdprConsent = { + gdprApplies: true, + consentString: "test-consent-string", + }; + + spec.buildRequests(validBidRequests, bidderRequest); + const result = spec.getUserSyncs( + { iframeEnabled: true }, + [], + gdprConsent + ); + expect(result[0].url).to.include("gdpr=1"); + expect(result[0].url).to.include("consent=test-consent-string"); + }); + + it("should handle US Privacy consent", function () { + const uspConsent = "1YNN"; + + spec.buildRequests(validBidRequests, bidderRequest); + const result = spec.getUserSyncs( + { iframeEnabled: true }, + [], + {}, + uspConsent + ); + + expect(result[0].url).to.include("us_privacy=1YNN"); + }); + + it("should handle GPP", function () { + const gppConsent = { + applicableSections: [7], + gppString: "test-consent-string", + }; + + spec.buildRequests(validBidRequests, bidderRequest); + const result = spec.getUserSyncs( + { iframeEnabled: true }, + [], + {}, + "", + gppConsent + ); + + expect(result[0].url).to.include("gpp=test-consent-string"); + expect(result[0].url).to.include("gpp_sid=7"); + }); + + it("should return empty array when sync is not enabled", function () { + const syncOptions = { + iframeEnabled: false, + pixelEnabled: false, + }; + + const result = spec.getUserSyncs(syncOptions, [], {}); + + expect(result).to.be.an("array").that.is.empty; + }); + }); }); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js new file mode 100644 index 00000000000..ac572027900 --- /dev/null +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -0,0 +1,1112 @@ +import {expect} from 'chai'; +import {spec} from 'modules/trustxBidAdapter.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import sinon from 'sinon'; +import {config} from 'src/config.js'; + +const getBannerRequest = () => { + return { + bidderCode: 'trustx', + auctionId: 'ca09c8cd-3824-4322-9dfe-d5b62b51c81c', + bidderRequestId: 'trustx-request-1', + bids: [ + { + bidder: 'trustx', + params: { + uid: '987654', + bidfloor: 5.25, + }, + auctionId: 'auction-id-45fe-9823-123456789abc', + placementCode: 'div-gpt-ad-trustx-test', + mediaTypes: { + banner: { + sizes: [ + [ 300, 250 ], + ] + } + }, + bidId: 'trustx-bid-12345', + bidderRequestId: 'trustx-request-1', + } + ], + start: 1615982436070, + auctionStart: 1615982436069, + timeout: 2000 + } +}; + +const getVideoRequest = () => { + return { + bidderCode: 'trustx', + auctionId: 'd2b62784-f134-4896-a87e-a233c3371413', + bidderRequestId: 'trustx-video-request-1', + bids: [{ + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'trustx', + sizes: [640, 480], + bidId: 'trustx-video-bid-1', + adUnitCode: 'video-placement-1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 789, + rewarded: 0, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 456 + }, + site: { + id: 1234, + page: 'https://trustx-test.com', + referrer: 'http://trustx-referrer.com' + }, + publisher_id: 'trustx-publisher-id', + bidfloor: 7.25, + } + }, { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'trustx', + sizes: [640, 480], + bidId: 'trustx-video-bid-2', + adUnitCode: 'video-placement-2', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 790, + rewarded: 0, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 457 + }, + site: { + id: 1235, + page: 'https://trustx-test2.com', + referrer: 'http://trustx-referrer2.com' + }, + publisher_id: 'trustx-publisher-id', + bidfloor: 8.50, + } + }], + auctionStart: 1615982456880, + timeout: 3500, + start: 1615982456884, + doneCbCallCount: 0, + refererInfo: { + numIframes: 1, + reachedTop: true, + referer: 'trustx-test.com' + } + }; +}; + +const getBidderResponse = () => { + return { + headers: null, + body: { + id: 'trustx-response-id-1', + seatbid: [ + { + bid: [ + { + id: 'trustx-bid-12345', + impid: 'trustx-bid-12345', + price: 3.22, + adm: '', + adid: '987654321', + adomain: [ + 'https://trustx-advertiser.com' + ], + iurl: 'https://trustx-campaign.com/creative.jpg', + cid: '12345', + crid: 'trustx-creative-234', + cat: [], + w: 300, + h: 250, + ext: { + prebid: { + type: 'banner' + }, + bidder: { + trustx: { + brand_id: 123456, + auction_id: 987654321098765, + bidder_id: 5, + bid_ad_type: 0 + } + } + } + } + ], + seat: 'trustx' + } + ], + ext: { + usersync: { + sync1: { + status: 'none', + syncs: [ + { + url: 'https://sync1.trustx.org/sync', + type: 'iframe' + } + ] + }, + sync2: { + status: 'none', + syncs: [ + { + url: 'https://sync2.trustx.org/sync', + type: 'pixel' + } + ] + } + }, + responsetimemillis: { + trustx: 95 + } + } + } + }; +} + +describe('trustxBidAdapter', function() { + let videoBidRequest; + + const VIDEO_REQUEST = { + 'bidderCode': 'trustx', + 'auctionId': 'd2b62784-f134-4896-a87e-a233c3371413', + 'bidderRequestId': 'trustx-video-request-1', + 'bids': videoBidRequest, + 'auctionStart': 1615982456880, + 'timeout': 3000, + 'start': 1615982456884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'trustx-test.com' + } + }; + + beforeEach(function () { + videoBidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'trustx', + sizes: [640, 480], + bidId: 'trustx-video-bid-1', + adUnitCode: 'video-placement-1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 789, + rewarded: 0, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 456 + }, + site: { + id: 1234, + page: 'https://trustx-test.com', + referrer: 'http://trustx-referrer.com' + }, + publisher_id: 'trustx-publisher-id', + bidfloor: 0 + } + }; + }); + + describe('isValidRequest', function() { + let bidderRequest; + + beforeEach(function() { + bidderRequest = getBannerRequest(); + }); + + it('should accept request with uid/secid', function () { + bidderRequest.bids[0].params = { + uid: '123', + mediaTypes: { banner: { sizes: [[300, 250]] } } + }; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.true; + }); + + it('should accept request with secid', function () { + bidderRequest.bids[0].params = { + secid: '456', + publisher_id: 'pub-1', + mediaTypes: { banner: { sizes: [[300, 250]] } } + }; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.true; + }); + + it('reject requests without params', function () { + bidderRequest.bids[0].params = {}; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.false; + }); + + it('returns false when banner mediaType does not exist', function () { + bidderRequest.bids[0].mediaTypes = {} + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.false; + }); + }); + + describe('buildRequests', function() { + let bidderRequest; + + beforeEach(function() { + bidderRequest = getBannerRequest(); + }); + + it('should return expected request object', function() { + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(bidRequest.url).equal('https://ads.trustx.org/pbhb'); + expect(bidRequest.method).equal('POST'); + }); + }); + + context('banner validation', function () { + let bidderRequest; + + beforeEach(function() { + bidderRequest = getBannerRequest(); + }); + + it('returns true when banner sizes are defined', function () { + const bid = { + bidder: 'trustx', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + uid: 'trustx-placement-1', + } + }; + + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.true; + }); + + it('returns false when banner sizes are invalid', function () { + const invalidSizes = [ + undefined, + '3:2', + 456, + 'invalid' + ]; + + invalidSizes.forEach((sizes) => { + const bid = { + bidder: 'trustx', + mediaTypes: { + banner: { + sizes + } + }, + params: { + uid: 'trustx-placement-1', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + }); + + context('video validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'trustx', + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'instream', + mimes: ['video/mp4', 'video/webm'], + protocols: [2, 3] + } + }, + params: { + uid: 'trustx-placement-1', + } + }; + }); + + it('should return true (skip validations) when test = true', function () { + this.bid.params = { + test: true + }; + expect(spec.isBidRequestValid(this.bid)).to.equal(true); + }); + + it('returns false when video context is not defined', function () { + delete this.bid.mediaTypes.video.context; + + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when video playserSize is invalid', function () { + const invalidSizes = [ + undefined, + '1:1', + 456, + 'invalid' + ]; + + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video mimes is invalid', function () { + const invalidMimes = [ + undefined, + 'invalid', + 1, + [] + ] + + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video protocols is invalid', function () { + const invalidProtocols = [ + undefined, + 'invalid', + 1, + [] + ] + + invalidProtocols.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('should accept outstream context', function () { + this.bid.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(this.bid)).to.be.true; + }); + }); + + describe('buildRequests', function () { + let bidderBannerRequest; + let bidRequestsWithMediaTypes; + let mockBidderRequest; + + beforeEach(function() { + bidderBannerRequest = getBannerRequest(); + + mockBidderRequest = {refererInfo: {}}; + + bidRequestsWithMediaTypes = [{ + bidder: 'trustx', + params: { + publisher_id: 'trustx-publisher-id', + }, + adUnitCode: '/adunit-test/trustx-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'trustx-test-bid-1', + bidderRequestId: 'trustx-test-request-1', + auctionId: 'trustx-test-auction-1', + transactionId: 'trustx-test-transaction-1', + ortb2Imp: { + ext: { + ae: 3 + } + } + }, { + bidder: 'trustx', + params: { + publisher_id: 'trustx-publisher-id', + }, + adUnitCode: 'trustx-adunit', + mediaTypes: { + video: { + playerSize: [640, 480], + placement: 1, + plcmt: 1, + } + }, + bidId: 'trustx-test-bid-2', + bidderRequestId: 'trustx-test-request-2', + auctionId: 'trustx-test-auction-2', + transactionId: 'trustx-test-transaction-2' + }]; + }); + + context('when mediaType is banner', function () { + it('creates request data', function () { + let request = spec.buildRequests(bidderBannerRequest.bids, bidderBannerRequest) + + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', bidderBannerRequest.bids[0].bidId); + }); + + it('should combine multiple bid requests into a single request', function () { + // NOTE: This test verifies that trustx adapter does NOT use multi-request logic. + // Trustx adapter = returns single request with all imp objects (standard OpenRTB) + // + // IMPORTANT: Trustx adapter DOES support multi-bid (multiple bids in response for one imp), + // but does NOT support multi-request (multiple requests instead of one). + const multipleBidRequests = [ + { + bidder: 'trustx', + params: { uid: 'uid-1' }, + adUnitCode: 'adunit-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + bidId: 'bid-1', + bidderRequestId: 'request-1', + auctionId: 'auction-1' + }, + { + bidder: 'trustx', + params: { uid: 'uid-2' }, + adUnitCode: 'adunit-2', + mediaTypes: { banner: { sizes: [[728, 90]] } }, + bidId: 'bid-2', + bidderRequestId: 'request-1', + auctionId: 'auction-1' + }, + { + bidder: 'trustx', + params: { uid: 'uid-3' }, + adUnitCode: 'adunit-3', + mediaTypes: { banner: { sizes: [[970, 250]] } }, + bidId: 'bid-3', + bidderRequestId: 'request-1', + auctionId: 'auction-1' + } + ]; + + const request = spec.buildRequests(multipleBidRequests, mockBidderRequest); + + // Trustx adapter should return a SINGLE request object + expect(request).to.be.an('object'); + expect(request).to.not.be.an('array'); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://ads.trustx.org/pbhb'); // No placement_id in URL + + // All imp objects should be in the same request + const payload = request.data; + expect(payload.imp).to.be.an('array'); + expect(payload.imp).to.have.length(3); + expect(payload.imp[0].id).to.equal('bid-1'); + expect(payload.imp[1].id).to.equal('bid-2'); + expect(payload.imp[2].id).to.equal('bid-3'); + expect(payload.imp[0].tagid).to.equal('uid-1'); + expect(payload.imp[1].tagid).to.equal('uid-2'); + expect(payload.imp[2].tagid).to.equal('uid-3'); + }); + + it('should determine media type from mtype field for banner', function () { + const customBidderResponse = Object.assign({}, getBidderResponse()); + customBidderResponse.body = Object.assign({}, getBidderResponse().body); + + if (customBidderResponse.body.seatbid && + customBidderResponse.body.seatbid[0] && + customBidderResponse.body.seatbid[0].bid && + customBidderResponse.body.seatbid[0].bid[0]) { + // Add mtype to the bid + customBidderResponse.body.seatbid[0].bid[0].mtype = 1; // Banner type + } + + const bidRequest = spec.buildRequests(bidderBannerRequest.bids, bidderBannerRequest); + const bids = spec.interpretResponse(customBidderResponse, bidRequest); + expect(bids[0].mediaType).to.equal('banner'); + }); + }); + + if (FEATURES.VIDEO) { + context('video', function () { + it('should create a POST request for every bid', function () { + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(requests.method).to.equal('POST'); + expect(requests.url.trim()).to.equal(spec.ENDPOINT); + }); + + it('should attach request data', function () { + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const data = requests.data; + const [width, height] = videoBidRequest.sizes; + const VERSION = '1.0.0'; + + expect(data.imp[1].video.w).to.equal(width); + expect(data.imp[1].video.h).to.equal(height); + expect(data.imp[1].bidfloor).to.equal(videoBidRequest.params.bidfloor); + expect(data.imp[1]['video']['placement']).to.equal(videoBidRequest.params.video['placement']); + expect(data.imp[1]['video']['plcmt']).to.equal(videoBidRequest.params.video['plcmt']); + expect(data.ext.prebidver).to.equal('$prebid.version$'); + expect(data.ext.adapterver).to.equal(spec.VERSION); + }); + + it('should attach End 2 End test data', function () { + bidRequestsWithMediaTypes[1].params.test = true; + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const data = requests.data; + expect(data.imp[1].bidfloor).to.equal(0); + expect(data.imp[1].video.w).to.equal(640); + expect(data.imp[1].video.h).to.equal(480); + }); + }); + } + + context('privacy regulations', function() { + it('should include USP consent data in request', function() { + const uspConsent = '1YNN'; + const bidderRequestWithUsp = Object.assign({}, mockBidderRequest, { uspConsent }); + const requests = spec.buildRequests(bidRequestsWithMediaTypes, bidderRequestWithUsp); + const data = requests.data; + + expect(data.regs.ext).to.have.property('us_privacy', '1YNN'); + }); + + it('should include GPP consent data from gppConsent in request', function() { + const gppConsent = { + gppString: 'GPP_CONSENT_STRING', + applicableSections: [1, 2, 3] + }; + + const bidderRequestWithGpp = Object.assign({}, mockBidderRequest, { gppConsent }); + const requests = spec.buildRequests(bidRequestsWithMediaTypes, bidderRequestWithGpp); + const data = requests.data; + + expect(data.regs).to.have.property('gpp', 'GPP_CONSENT_STRING'); + expect(data.regs.gpp_sid).to.deep.equal([1, 2, 3]); + }); + + it('should include GPP consent data from ortb2 in request', function() { + const ortb2 = { + regs: { + gpp: 'GPP_STRING_FROM_ORTB2', + gpp_sid: [1, 2] + } + }; + + const bidderRequestWithOrtb2Gpp = Object.assign({}, mockBidderRequest, { ortb2 }); + const requests = spec.buildRequests(bidRequestsWithMediaTypes, bidderRequestWithOrtb2Gpp); + const data = requests.data; + + expect(data.regs).to.have.property('gpp', 'GPP_STRING_FROM_ORTB2'); + expect(data.regs.gpp_sid).to.deep.equal([1, 2]); + }); + + it('should prioritize gppConsent over ortb2 for GPP consent data', function() { + const gppConsent = { + gppString: 'GPP_CONSENT_STRING', + applicableSections: [1, 2, 3] + }; + + const ortb2 = { + regs: { + gpp: 'GPP_STRING_FROM_ORTB2', + gpp_sid: [1, 2] + } + }; + + const bidderRequestWithBothGpp = Object.assign({}, mockBidderRequest, { gppConsent, ortb2 }); + const requests = spec.buildRequests(bidRequestsWithMediaTypes, bidderRequestWithBothGpp); + const data = requests.data; + + expect(data.regs).to.have.property('gpp', 'GPP_CONSENT_STRING'); + expect(data.regs.gpp_sid).to.deep.equal([1, 2, 3]); + }); + + it('should include COPPA flag in request when set to true', function() { + // Mock the config.getConfig function to return true for coppa + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const data = requests.data; + + expect(data.regs).to.have.property('coppa', 1); + + // Restore the stub + config.getConfig.restore(); + }); + }); + }); + + describe('interpretResponse', function() { + context('when mediaType is banner', function() { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getBannerRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, bidderResponse, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; + }); + + it('have bids', function () { + let bids = spec.interpretResponse(bidderResponse, bidRequest); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', getBidderResponse().body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', getBidderResponse().body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', getBidderResponse().body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', getBidderResponse().body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', getBidderResponse().body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', getBidderResponse().body.seatbid[0].bid[index].crid); + expect(bids[index].meta).to.have.property('advertiserDomains'); + expect(bids[index]).to.have.property('ttl', 360); + expect(bids[index]).to.have.property('netRevenue', false); + } + }); + + it('should determine media type from mtype field for banner', function () { + const customBidderResponse = Object.assign({}, getBidderResponse()); + customBidderResponse.body = Object.assign({}, getBidderResponse().body); + + if (customBidderResponse.body.seatbid && + customBidderResponse.body.seatbid[0] && + customBidderResponse.body.seatbid[0].bid && + customBidderResponse.body.seatbid[0].bid[0]) { + // Add mtype to the bid + customBidderResponse.body.seatbid[0].bid[0].mtype = 1; // Banner type + } + + const bids = spec.interpretResponse(customBidderResponse, bidRequest); + expect(bids[0].mediaType).to.equal('banner'); + }); + + it('should support multi-bid (multiple bids for one imp object) - Prebid v10 feature', function () { + // Multi-bid: Server can return multiple bids for a single imp object + // This is supported by ortbConverter which trustx adapter uses + const multiBidResponse = { + headers: null, + body: { + id: 'trustx-response-multi-bid', + seatbid: [ + { + bid: [ + { + id: 'bid-1', + impid: 'trustx-bid-12345', // Same impid - multiple bids for one imp + price: 2.50, + adm: '
Bid 1 Creative
', + adid: 'ad-1', + crid: 'creative-1', + w: 300, + h: 250, + mtype: 1, // Banner + ext: { + prebid: { type: 'banner' } + } + }, + { + id: 'bid-2', + impid: 'trustx-bid-12345', // Same impid - multiple bids for one imp + price: 3.00, + adm: '
Bid 2 Creative
', + adid: 'ad-2', + crid: 'creative-2', + w: 300, + h: 250, + mtype: 1, // Banner + ext: { + prebid: { type: 'banner' } + } + }, + { + id: 'bid-3', + impid: 'trustx-bid-12345', // Same impid - multiple bids for one imp + price: 2.75, + adm: '
Bid 3 Creative
', + adid: 'ad-3', + crid: 'creative-3', + w: 300, + h: 250, + mtype: 1, // Banner + ext: { + prebid: { type: 'banner' } + } + } + ], + seat: 'trustx' + } + ] + } + }; + + const bids = spec.interpretResponse(multiBidResponse, bidRequest); + + // Trustx adapter should return all bids (multi-bid support via ortbConverter) + expect(bids).to.be.an('array'); + expect(bids).to.have.length(3); // All 3 bids should be returned + + // Verify each bid + expect(bids[0].requestId).to.equal('trustx-bid-12345'); + expect(bids[0].cpm).to.equal(2.50); + expect(bids[0].ad).to.equal('
Bid 1 Creative
'); + expect(bids[0].creativeId).to.equal('creative-1'); + + expect(bids[1].requestId).to.equal('trustx-bid-12345'); + expect(bids[1].cpm).to.equal(3.00); + expect(bids[1].ad).to.equal('
Bid 2 Creative
'); + expect(bids[1].creativeId).to.equal('creative-2'); + + expect(bids[2].requestId).to.equal('trustx-bid-12345'); + expect(bids[2].cpm).to.equal(2.75); + expect(bids[2].ad).to.equal('
Bid 3 Creative
'); + expect(bids[2].creativeId).to.equal('creative-3'); + }); + + it('should handle response with ext.userid (userid is saved to localStorage by adapter)', function () { + const userId = '42d55fac-da65-4468-b854-06f40cfe3852'; + const impId = bidRequest.data.imp[0].id; + const responseWithUserId = { + headers: null, + body: { + id: 'trustx-response-with-userid', + seatbid: [{ + bid: [{ + id: 'bid-1', + impid: impId, + price: 0.5, + adm: '
Test Ad
', + w: 300, + h: 250, + mtype: 1 + }], + seat: 'trustx' + }], + ext: { + userid: userId + } + } + }; + + // Test that interpretResponse handles ext.userid without errors + // (actual localStorage saving is tested at integration level) + const bids = spec.interpretResponse(responseWithUserId, bidRequest); + + expect(bids).to.be.an('array').that.is.not.empty; + expect(bids[0].cpm).to.equal(0.5); + // ext.userid is processed internally by adapter and saved to localStorage + // if localStorageWriteAllowed is true and localStorage is enabled + }); + + it('should handle response with nurl and burl tracking URLs', function () { + const impId = bidRequest.data.imp[0].id; + const nurl = 'https://trackers.trustx.org/event?brid=xxx&e=nurl&cpm=${AUCTION_PRICE}'; + const burl = 'https://trackers.trustx.org/event?brid=xxx&e=burl&cpm=${AUCTION_PRICE}'; + const responseWithTracking = { + headers: null, + body: { + id: 'trustx-response-tracking', + seatbid: [{ + bid: [{ + id: 'bid-1', + impid: impId, + price: 0.0413, + adm: '
Test Ad
', + nurl: nurl, + burl: burl, + adid: 'z0bgu851', + w: 728, + h: 90, + mtype: 1 + }], + seat: 'trustx' + }] + } + }; + + const bids = spec.interpretResponse(responseWithTracking, bidRequest); + + expect(bids).to.be.an('array').that.is.not.empty; + // burl is directly mapped to bidResponse.burl by ortbConverter + expect(bids[0].burl).to.equal(burl); + // nurl is processed by banner processor: if adm exists, nurl is embedded as tracking pixel in ad + // if no adm, nurl becomes adUrl. In this case, we have adm, so nurl is in ad as tracking pixel + expect(bids[0].ad).to.include(nurl); // nurl should be embedded in ad as tracking pixel + expect(bids[0].ad).to.include('
Test Ad
'); // original adm should still be there + }); + + it('should handle response with adomain and cat (categories)', function () { + const impId = bidRequest.data.imp[0].id; + const responseWithCategories = { + headers: null, + body: { + id: 'trustx-response-categories', + seatbid: [{ + bid: [{ + id: 'bid-1', + impid: impId, + price: 0.5, + adm: '
Test Ad
', + adid: 'z0bgu851', + adomain: ['adl.org'], + cat: ['IAB6'], + w: 728, + h: 90, + mtype: 1 + }], + seat: 'trustx' + }] + } + }; + + const bids = spec.interpretResponse(responseWithCategories, bidRequest); + + expect(bids).to.be.an('array').that.is.not.empty; + expect(bids[0].meta).to.exist; + expect(bids[0].meta.advertiserDomains).to.deep.equal(['adl.org']); + expect(bids[0].meta.primaryCatId).to.equal('IAB6'); + }); + }); + + context('when mediaType is video', function () { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getVideoRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, bidderResponse, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; + }); + + it('should return no bids if the response "nurl" and "adm" are missing', function () { + const SERVER_RESP = Object.assign({}, bidderResponse, {'body': { + seatbid: [{ + bid: [{ + price: 8.01 + }] + }] + }}); + const bids = spec.interpretResponse(SERVER_RESP, bidRequest); + expect(bids.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', function () { + const SERVER_RESP = Object.assign({}, bidderResponse, {'body': { + seatbid: [{ + bid: [{ + adm: '' + }] + }] + }}); + const bids = spec.interpretResponse(SERVER_RESP, bidRequest); + expect(bids.length).to.equal(0); + }); + + it('should determine media type from mtype field for video', function () { + const SERVER_RESP = Object.assign({}, bidderResponse, { + 'body': { + seatbid: [{ + bid: [{ + id: 'trustx-video-bid-1', + impid: 'trustx-video-bid-1', + price: 10.00, + adm: '', + adid: '987654321', + adomain: ['trustx-advertiser.com'], + iurl: 'https://trustx-campaign.com/creative.jpg', + cid: '12345', + crid: 'trustx-creative-234', + w: 1920, + h: 1080, + mtype: 2, // Video type + ext: { + prebid: { + type: 'video' + } + } + }] + }] + } + }); + + const bids = spec.interpretResponse(SERVER_RESP, bidRequest); + expect(bids[0].mediaType).to.equal('video'); + }); + }); + }); + + describe('getUserSyncs', function () { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getVideoRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [bidderResponse]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(bidderResponse.body.ext.usersync['sync1'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [bidderResponse]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(bidderResponse.body.ext.usersync['sync2'].syncs[0].url); + }); + + it('all sync enabled should prioritize iframe', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [bidderResponse]); + + expect(opts.length).to.equal(1); + }); + + it('should handle real-world usersync with multiple providers (ttd, mediaforce, bidswitch, trustx)', function () { + const realWorldResponse = { + headers: null, + body: { + id: '40998eeaaba56068', + seatbid: [{ + bid: [{ + id: '435ddfcec96f2ab', + impid: '435ddfcec96f2ab', + price: 0.0413, + adm: '
Test Ad
', + w: 728, + h: 90, + mtype: 1 + }], + seat: '15' + }], + ext: { + usersync: { + ttd: { + syncs: [ + { + url: 'https://match.adsrvr.org/track/cmf/generic?ttd_tpi=1&ttd_pid=lewrkpm', + type: 'pixel' + }, + { + url: 'https://match.adsrvr.org/track/cmf/generic?ttd_tpi=1&ttd_pid=q4jldgr', + type: 'pixel' + } + ] + }, + mediaforce: { + syncs: [ + { + url: 'https://rtb.mfadsrvr.com/sync?ssp=trustx', + type: 'pixel' + } + ] + }, + bidswitch: { + syncs: [ + { + url: 'https://x.bidswitch.net/sync?ssp=trustx&user_id=42d55fac-da65-4468-b854-06f40cfe3852', + type: 'pixel' + } + ] + }, + trustx: { + syncs: [ + { + url: 'https://sync.trustx.org/usync?gdpr=&gdpr_consent=&us_privacy=1---&gpp=&gpp_sid=&publisher_id=101452&source=pbjs-response', + type: 'pixel' + }, + { + url: 'https://static.cdn.trustx.org/x/user_sync.html?gdpr=&gdpr_consent=&us_privacy=1---&gpp=&gpp_sid=&publisher_id=101452&source=pbjs-response', + type: 'iframe' + } + ] + } + }, + userid: '42d55fac-da65-4468-b854-06f40cfe3852' + } + } + }; + + // Test with pixel enabled + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [realWorldResponse]); + + // Should return all pixel syncs from all providers + expect(opts).to.be.an('array'); + expect(opts.length).to.equal(5); // 2 ttd + 1 mediaforce + 1 bidswitch + 1 trustx pixel + expect(opts.every(s => s.type === 'image')).to.be.true; + expect(opts.some(s => s.url.includes('adsrvr.org'))).to.be.true; + expect(opts.some(s => s.url.includes('mfadsrvr.com'))).to.be.true; + expect(opts.some(s => s.url.includes('bidswitch.net'))).to.be.true; + expect(opts.some(s => s.url.includes('sync.trustx.org'))).to.be.true; + + // Test with iframe enabled + opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [realWorldResponse]); + + // Should return only iframe syncs + expect(opts).to.be.an('array'); + expect(opts.length).to.equal(1); // 1 trustx iframe + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.include('static.cdn.trustx.org'); + }); + }); +}); diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index a90972d4163..f8980bd3961 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -35,7 +35,7 @@ const makeUid2IdentityContainer = (token) => ({uid2: {id: token}}); const makeUid2OptoutContainer = (token) => ({uid2: {optout: true}}); let useLocalStorage = false; const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ - userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings + userSync: { auctionDelay: extraSettings.auctionDelay ?? auctionDelayMs, ...(extraSettings.syncDelay !== undefined && {syncDelay: extraSettings.syncDelay}), userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug }); const makeOriginalIdentity = (identity, salt = 1) => ({ identity: utils.cyrb53Hash(identity, salt), diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index d73b9b6e8c7..38ec126bb73 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -42,20 +42,7 @@ describe('UnrulyAdapter', function () { } } - function createOutStreamExchangeAuctionConfig() { - return { - 'seller': 'https://nexxen.tech', - 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', - 'interestGroupBuyers': 'https://mydsp.com', - 'perBuyerSignals': { - 'https://mydsp.com': { - 'floor': 'bouttreefiddy' - } - } - } - }; - - function createExchangeResponse (bidList, auctionConfigs = null) { + function createExchangeResponse (bidList) { let bids = []; if (Array.isArray(bidList)) { bids = bidList; @@ -63,18 +50,9 @@ describe('UnrulyAdapter', function () { bids.push(bidList); } - if (!auctionConfigs) { - return { - 'body': {bids} - }; - } - return { - 'body': { - bids, - auctionConfigs - } - } + 'body': {bids} + }; }; const inStreamServerResponse = { @@ -692,231 +670,6 @@ describe('UnrulyAdapter', function () { const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); expect(result[0].data).to.deep.equal(expectedResult); }); - describe('Protected Audience Support', function() { - it('should return an array with 2 items and enabled protected audience', function () { - mockBidRequests = { - 'bidderCode': 'unruly', - 'paapi': { - enabled: true - }, - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - }, - { - 'bidder': 'unruly', - 'params': { - 'siteId': 2234554, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - } - ] - }; - - const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); - expect(typeof result).to.equal('object'); - expect(result.length).to.equal(2); - expect(result[0].data.bidderRequest.bids.length).to.equal(1); - expect(result[1].data.bidderRequest.bids.length).to.equal(1); - expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); - expect(result[1].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); - }); - it('should return an array with 2 items and enabled protected audience on only one unit', function () { - mockBidRequests = { - 'bidderCode': 'unruly', - 'paapi': { - enabled: true - }, - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - }, - { - 'bidder': 'unruly', - 'params': { - 'siteId': 2234554, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': {} - } - } - ] - }; - - const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); - expect(typeof result).to.equal('object'); - expect(result.length).to.equal(2); - expect(result[0].data.bidderRequest.bids.length).to.equal(1); - expect(result[1].data.bidderRequest.bids.length).to.equal(1); - expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); - expect(result[1].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.be.undefined; - }); - it('disables configured protected audience when fledge is not availble', function () { - mockBidRequests = { - 'bidderCode': 'unruly', - 'fledgeEnabled': false, - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - } - ] - }; - - const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); - expect(typeof result).to.equal('object'); - expect(result.length).to.equal(1); - expect(result[0].data.bidderRequest.bids.length).to.equal(1); - expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.be.undefined; - }); - }); }); describe('interpretResponse', function () { @@ -967,166 +720,6 @@ describe('UnrulyAdapter', function () { ]); }); - it('should return object with an array of bids and an array of auction configs when it receives a successful response from server', function () { - const bidId = '27a3ee1626a5c7' - const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); - const mockExchangeAuctionConfig = {}; - mockExchangeAuctionConfig[bidId] = createOutStreamExchangeAuctionConfig(); - const mockServerResponse = createExchangeResponse(mockExchangeBid, mockExchangeAuctionConfig); - const originalRequest = { - 'data': { - 'bidderRequest': { - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 640, - 480 - ], - [ - 640, - 480 - ], - [ - 300, - 250 - ], - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'bidId': bidId, - 'bidderRequestId': '12e00d17dff07b', - } - ] - } - } - }; - - expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ - 'bids': [ - { - 'ext': { - 'statusCode': 1, - 'renderer': { - 'id': 'unruly_inarticle', - 'config': { - 'siteId': 123456, - 'targetingUUID': 'xxx-yyy-zzz' - }, - 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' - }, - 'adUnitCode': 'video1' - }, - requestId: 'mockBidId', - bidderCode: 'unruly', - cpm: 20, - width: 323, - height: 323, - vastUrl: 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22https%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', - netRevenue: true, - creativeId: 'mockBidId', - ttl: 360, - 'meta': { - 'mediaType': 'video', - 'videoContext': 'outstream' - }, - currency: 'USD', - renderer: fakeRenderer, - mediaType: 'video' - } - ], - 'paapi': [{ - 'bidId': bidId, - 'config': { - 'seller': 'https://nexxen.tech', - 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', - 'interestGroupBuyers': 'https://mydsp.com', - 'perBuyerSignals': { - 'https://mydsp.com': { - 'floor': 'bouttreefiddy' - } - } - } - }] - }); - }); - - it('should return object with an array of auction configs when it receives a successful response from server without bids', function () { - const bidId = '27a3ee1626a5c7'; - const mockExchangeAuctionConfig = {}; - mockExchangeAuctionConfig[bidId] = createOutStreamExchangeAuctionConfig(); - const mockServerResponse = createExchangeResponse(null, mockExchangeAuctionConfig); - const originalRequest = { - 'data': { - 'bidderRequest': { - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 640, - 480 - ], - [ - 640, - 480 - ], - [ - 300, - 250 - ], - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'bidId': bidId, - 'bidderRequestId': '12e00d17dff07b' - } - ] - } - } - }; - - expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ - 'bids': [], - 'paapi': [{ - 'bidId': bidId, - 'config': { - 'seller': 'https://nexxen.tech', - 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', - 'interestGroupBuyers': 'https://mydsp.com', - 'perBuyerSignals': { - 'https://mydsp.com': { - 'floor': 'bouttreefiddy' - } - } - } - }] - }); - }); - it('should initialize and set the renderer', function () { expect(Renderer.install.called).to.be.false; expect(fakeRenderer.setRender.called).to.be.false; diff --git a/test/spec/modules/verbenBidAdapter_spec.js b/test/spec/modules/verbenBidAdapter_spec.js new file mode 100644 index 00000000000..9864e9d2b70 --- /dev/null +++ b/test/spec/modules/verbenBidAdapter_spec.js @@ -0,0 +1,478 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/verbenBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'verben'; + +describe('VerbenBidAdapter', 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://east-node.verben.com/pbjs'); + }); + + it('Returns general data valid', function () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + 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 + } + ]; + + const 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); + const 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); + const 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] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const 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]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + 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 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const 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 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const 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 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const 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' + }] + }; + + const 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' + }] + }; + const 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', + }] + }; + const 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' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index d17e82a1c7a..b76805e4ba0 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('VisibleMeasuresBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); 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') @@ -492,9 +492,7 @@ describe('VisibleMeasuresBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/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---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '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') @@ -503,7 +501,7 @@ describe('VisibleMeasuresBidAdapter', function () { 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({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js index fa6ea2e642f..c446142db1a 100644 --- a/test/spec/modules/wurflRtdProvider_spec.js +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -1,24 +1,41 @@ import { - bidderData, - enrichBidderRequest, - lowEntropyData, wurflSubmodule, - makeOrtb2DeviceType, - toNumber, + storage } from 'modules/wurflRtdProvider'; import * as ajaxModule from 'src/ajax'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; +import * as prebidGlobalModule from 'src/prebidGlobal.js'; +import { guardOrtb2Fragments } from 'libraries/objectGuard/ortbGuard.js'; +import { config } from 'src/config.js'; describe('wurflRtdProvider', function () { describe('wurflSubmodule', function () { const altHost = 'http://example.local/wurfl.js'; + // Global cleanup to ensure debug config doesn't leak between tests + afterEach(function () { + config.resetConfig(); + }); + const wurfl_pbjs = { - low_entropy_caps: ['is_mobile', 'complete_device_name', 'form_factor'], - caps: ['advertised_browser', 'advertised_browser_version', 'advertised_device_os', 'advertised_device_os_version', 'ajax_support_javascript', 'brand_name', 'complete_device_name', 'density_class', 'form_factor', 'is_android', 'is_app_webview', 'is_connected_tv', 'is_full_desktop', 'is_ios', 'is_mobile', 'is_ott', 'is_phone', 'is_robot', 'is_smartphone', 'is_smarttv', 'is_tablet', 'manufacturer_name', 'marketing_name', 'max_image_height', 'max_image_width', 'model_name', 'physical_screen_height', 'physical_screen_width', 'pixel_density', 'pointing_method', 'resolution_height', 'resolution_width'], - authorized_bidders: { - bidder1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], - bidder2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 21, 25, 28, 30, 31] + caps: ['wurfl_id', 'advertised_browser', 'advertised_browser_version', 'advertised_device_os', 'advertised_device_os_version', 'ajax_support_javascript', 'brand_name', 'complete_device_name', 'density_class', 'form_factor', 'is_android', 'is_app_webview', 'is_connected_tv', 'is_full_desktop', 'is_ios', 'is_mobile', 'is_ott', 'is_phone', 'is_robot', 'is_smartphone', 'is_smarttv', 'is_tablet', 'manufacturer_name', 'marketing_name', 'max_image_height', 'max_image_width', 'model_name', 'physical_screen_height', 'physical_screen_width', 'pixel_density', 'pointing_method', 'resolution_height', 'resolution_width'], + over_quota: 0, + sampling_rate: 100, + global: { + basic_set: { + cap_indices: [0, 9, 15, 16, 17, 18, 32] + }, + publisher: { + cap_indices: [1, 2, 3, 4, 5] + } + }, + bidders: { + bidder1: { + cap_indices: [6, 7, 8, 10, 11, 26, 27] + }, + bidder2: { + cap_indices: [12, 13, 14, 19, 20, 21, 22] + } } } const WURFL = { @@ -58,10 +75,12 @@ describe('wurflRtdProvider', function () { }; // expected analytics values - const expectedStatsURL = 'https://prebid.wurflcloud.com/v1/prebid/stats'; + const expectedStatsURL = 'https://stats.prebid.wurflcloud.com/v2/prebid/stats'; const expectedData = JSON.stringify({ bidders: ['bidder1', 'bidder2'] }); let sandbox; + // originalUserAgentData to restore after tests + let originalUAData; beforeEach(function () { sandbox = sinon.createSandbox(); @@ -69,12 +88,19 @@ describe('wurflRtdProvider', function () { init: new Promise(function (resolve, reject) { resolve({ WURFL, wurfl_pbjs }) }), complete: new Promise(function (resolve, reject) { resolve({ WURFL, wurfl_pbjs }) }), }; + originalUAData = window.navigator.userAgentData; + // Initialize module with clean state for each test + wurflSubmodule.init({ params: {} }); }); afterEach(() => { // Restore the original functions sandbox.restore(); window.WURFLPromises = undefined; + Object.defineProperty(window.navigator, 'userAgentData', { + value: originalUAData, + configurable: true, + }); }); // Bid request config @@ -94,396 +120,2426 @@ describe('wurflRtdProvider', function () { } }; - it('initialises the WURFL RTD provider', function () { - expect(wurflSubmodule.init()).to.be.true; - }); + // Client Hints tests + describe('Client Hints support', () => { + it('should collect and send client hints when available', (done) => { + const clock = sinon.useFakeTimers(); + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - it('should enrich the bid request data', (done) => { - const expectedURL = new URL(altHost); - expectedURL.searchParams.set('debug', true); - expectedURL.searchParams.set('mode', 'prebid'); - expectedURL.searchParams.set('wurfl_id', true); + // Mock Client Hints + const mockClientHints = { + architecture: 'arm', + bitness: '64', + model: 'Pixel 5', + platformVersion: '13.0.0', + uaFullVersion: '130.0.6723.58', + fullVersionList: [ + { brand: 'Chromium', version: '130.0.6723.58' } + ] + }; - const callback = () => { - const v = { - bidder1: { - device: { - make: 'Google', - model: 'Nexus 5', - devicetype: 1, - os: 'Android', - osv: '6.0', - hwv: 'Nexus 5', - h: 1920, - w: 1080, - ppi: 443, - pxratio: 3.0, - js: 1, - ext: { - wurfl: { - advertised_browser: 'Chrome Mobile', - advertised_browser_version: '130.0.0.0', - advertised_device_os: 'Android', - advertised_device_os_version: '6.0', - ajax_support_javascript: !0, - brand_name: 'Google', - complete_device_name: 'Google Nexus 5', - density_class: '3.0', - form_factor: 'Feature Phone', - is_app_webview: !1, - is_connected_tv: !1, - is_full_desktop: !1, - is_mobile: !0, - is_ott: !1, - is_phone: !0, - is_robot: !1, - is_smartphone: !1, - is_smarttv: !1, - is_tablet: !1, - manufacturer_name: 'LG', - marketing_name: '', - max_image_height: 640, - max_image_width: 360, - model_name: 'Nexus 5', - physical_screen_height: 110, - physical_screen_width: 62, - pixel_density: 443, - pointing_method: 'touchscreen', - resolution_height: 1920, - resolution_width: 1080, - wurfl_id: 'lg_nexus5_ver1', - }, - }, - }, - }, - bidder2: { - device: { - make: 'Google', - model: 'Nexus 5', - devicetype: 1, - os: 'Android', - osv: '6.0', - hwv: 'Nexus 5', - h: 1920, - w: 1080, - ppi: 443, - pxratio: 3.0, - js: 1, - ext: { - wurfl: { - advertised_device_os: 'Android', - advertised_device_os_version: '6.0', - ajax_support_javascript: !0, - brand_name: 'Google', - complete_device_name: 'Google Nexus 5', - density_class: '3.0', - form_factor: 'Feature Phone', - is_android: !0, - is_app_webview: !1, - is_connected_tv: !1, - is_full_desktop: !1, - is_ios: !1, - is_mobile: !0, - is_ott: !1, - is_phone: !0, - is_tablet: !1, - manufacturer_name: 'LG', - model_name: 'Nexus 5', - pixel_density: 443, - resolution_height: 1920, - resolution_width: 1080, - wurfl_id: 'lg_nexus5_ver1', - }, - }, - }, - }, - bidder3: { - device: { - make: 'Google', - model: 'Nexus 5', - ext: { - wurfl: { - complete_device_name: 'Google Nexus 5', - form_factor: 'Feature Phone', - is_mobile: !0, - model_name: 'Nexus 5', - brand_name: 'Google', - wurfl_id: 'lg_nexus5_ver1', - }, - }, - }, - }, + const getHighEntropyValuesStub = sandbox.stub().resolves(mockClientHints); + Object.defineProperty(navigator, 'userAgentData', { + value: { getHighEntropyValues: getHighEntropyValuesStub }, + configurable: true, + writable: true + }); + + // Empty cache to trigger async load + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = async () => { + // Verify client hints were requested + expect(getHighEntropyValuesStub.calledOnce).to.be.true; + expect(getHighEntropyValuesStub.calledWith( + ['architecture', 'bitness', 'model', 'platformVersion', 'uaFullVersion', 'fullVersionList'] + )).to.be.true; + + try { + // Use tickAsync to properly handle promise microtasks + await clock.tickAsync(1); + + // Now verify WURFL.js was loaded with client hints in URL + expect(loadExternalScriptStub.called).to.be.true; + const scriptUrl = loadExternalScriptStub.getCall(0).args[0]; + + const url = new URL(scriptUrl); + const uachParam = url.searchParams.get('uach'); + expect(uachParam).to.not.be.null; + + const parsedHints = JSON.parse(uachParam); + expect(parsedHints).to.deep.equal(mockClientHints); + + clock.restore(); + done(); + } catch (err) { + clock.restore(); + done(err); + } }; - expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal(v); - done(); - }; - const config = { - params: { - altHost: altHost, - debug: true, - } - }; - const userConsent = {}; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }) + it('should load WURFL.js without client hints when not available', (done) => { + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); - expect(loadExternalScriptStub.calledOnce).to.be.true; - const loadExternalScriptCall = loadExternalScriptStub.getCall(0); - expect(loadExternalScriptCall.args[0]).to.equal(expectedURL.toString()); - expect(loadExternalScriptCall.args[2]).to.equal('wurfl'); - }); + // No client hints available + Object.defineProperty(navigator, 'userAgentData', { + value: undefined, + configurable: true, + writable: true + }); - it('onAuctionEndEvent: should send analytics data using navigator.sendBeacon, if available', () => { - const auctionDetails = {}; - const config = {}; - const userConsent = {}; + // Empty cache to trigger async load + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); - const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon'); + const callback = () => { + // Verify WURFL.js was loaded without uach parameter + expect(loadExternalScriptStub.calledOnce).to.be.true; + const scriptUrl = loadExternalScriptStub.getCall(0).args[0]; - // Call the function - wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + const url = new URL(scriptUrl); + const uachParam = url.searchParams.get('uach'); + expect(uachParam).to.be.null; + + done(); + }; - // Assertions - expect(sendBeaconStub.calledOnce).to.be.true; - expect(sendBeaconStub.calledWithExactly(expectedStatsURL, expectedData)).to.be.true; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); }); - it('onAuctionEndEvent: should send analytics data using fetch as fallback, if navigator.sendBeacon is not available', () => { - const auctionDetails = {}; - const config = {}; - const userConsent = {}; + // TTL handling tests + describe('TTL handling', () => { + it('should use valid (not expired) cached data without triggering async load', (done) => { + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon').value(undefined); - const windowFetchStub = sandbox.stub(window, 'fetch'); - const fetchAjaxStub = sandbox.stub(ajaxModule, 'fetch'); + // Setup cache with valid TTL (expires in future) + const futureExpiry = Date.now() + 1000000; // expires in future + const cachedData = { + WURFL, + wurfl_pbjs: { ...wurfl_pbjs, ttl: 2592000 }, + expire_at: futureExpiry + }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); - // Call the function - wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + const callback = () => { + // Verify global FPD enrichment happened (not over quota) + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4 + }); - // Assertions - expect(sendBeaconStub.called).to.be.false; + // Verify no async load was triggered (cache is valid) + expect(loadExternalScriptStub.called).to.be.false; - expect(fetchAjaxStub.calledOnce).to.be.true; - const fetchAjaxCall = fetchAjaxStub.getCall(0); - expect(fetchAjaxCall.args[0]).to.equal(expectedStatsURL); - expect(fetchAjaxCall.args[1].method).to.equal('POST'); - expect(fetchAjaxCall.args[1].body).to.equal(expectedData); - expect(fetchAjaxCall.args[1].mode).to.equal('no-cors'); - }); - }); + done(); + }; - describe('bidderData', () => { - it('should return the WURFL data for a bidder', () => { - const wjsData = { - capability1: 'value1', - capability2: 'value2', - capability3: 'value3', - }; - const caps = ['capability1', 'capability2', 'capability3']; - const filter = [0, 2]; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); - const result = bidderData(wjsData, caps, filter); + it('should use expired cached data and trigger async refresh (without Client Hints)', (done) => { + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - expect(result).to.deep.equal({ - capability1: 'value1', - capability3: 'value3', + Object.defineProperty(navigator, 'userAgentData', { + value: undefined, + configurable: true, + writable: true + }); + // Setup cache with expired TTL + const pastExpiry = Date.now() - 1000; // expired 1 second ago + const cachedData = { + WURFL, + wurfl_pbjs: { ...wurfl_pbjs, ttl: 2592000 }, + expire_at: pastExpiry + }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = () => { + // Verify expired cache data is still used for enrichment + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4 + }); + + // Verify bidders were enriched + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2).to.exist; + + // Verify async load WAS triggered for refresh (cache expired) + expect(loadExternalScriptStub.calledOnce).to.be.true; + + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); }); }); - it('should return an empty object if the filter is empty', () => { - const wjsData = { - capability1: 'value1', - capability2: 'value2', - capability3: 'value3', - }; - const caps = ['capability1', 'capability3']; - const filter = []; + // Debug mode initialization tests + describe('Debug mode', () => { + afterEach(() => { + // Clean up window object after each test + delete window.WurflRtdDebug; + // Reset global config + config.resetConfig(); + }); - const result = bidderData(wjsData, caps, filter); + it('should not create window.WurflRtdDebug when global debug=false', () => { + config.setConfig({ debug: false }); + const moduleConfig = { params: {} }; + wurflSubmodule.init(moduleConfig); + expect(window.WurflRtdDebug).to.be.undefined; + }); - expect(result).to.deep.equal({}); - }); - }); + it('should not create window.WurflRtdDebug when global debug is not configured', () => { + config.resetConfig(); + const moduleConfig = { params: {} }; + wurflSubmodule.init(moduleConfig); + expect(window.WurflRtdDebug).to.be.undefined; + }); - describe('lowEntropyData', () => { - it('should return the correct low entropy data for Apple devices', () => { - const wjsData = { - complete_device_name: 'Apple iPhone X', - form_factor: 'Smartphone', - is_mobile: !0, - brand_name: 'Apple', - model_name: 'iPhone X', - }; - const lowEntropyCaps = ['complete_device_name', 'form_factor', 'is_mobile']; - const expectedData = { - complete_device_name: 'Apple iPhone', - form_factor: 'Smartphone', - is_mobile: !0, - brand_name: 'Apple', - model_name: 'iPhone', - }; - const result = lowEntropyData(wjsData, lowEntropyCaps); - expect(result).to.deep.equal(expectedData); + it('should create window.WurflRtdDebug when global debug=true', () => { + config.setConfig({ debug: true }); + const moduleConfig = { params: {} }; + wurflSubmodule.init(moduleConfig); + expect(window.WurflRtdDebug).to.exist; + expect(window.WurflRtdDebug.dataSource).to.equal('unknown'); + expect(window.WurflRtdDebug.cacheExpired).to.be.false; + }); }); - it('should return the correct low entropy data for Android devices', () => { - const wjsData = { - complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', - form_factor: 'Smartphone', - is_mobile: !0, - }; - const lowEntropyCaps = ['complete_device_name', 'form_factor', 'is_mobile']; - const expectedData = { - complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', - form_factor: 'Smartphone', - is_mobile: !0, - }; - const result = lowEntropyData(wjsData, lowEntropyCaps); - expect(result).to.deep.equal(expectedData); + it('initialises the WURFL RTD provider', function () { + expect(wurflSubmodule.init()).to.be.true; }); - it('should return an empty object if the lowEntropyCaps array is empty', () => { - const wjsData = { - complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', - form_factor: 'Smartphone', - is_mobile: !0, - }; - const lowEntropyCaps = []; - const expectedData = {}; - const result = lowEntropyData(wjsData, lowEntropyCaps); - expect(result).to.deep.equal(expectedData); - }); - }); + describe('A/B testing', () => { + it('should return true when A/B testing is disabled', () => { + const config = { params: { abTest: false } }; + expect(wurflSubmodule.init(config)).to.be.true; + }); - describe('enrichBidderRequest', () => { - it('should enrich the bidder request with WURFL data', () => { - const reqBidsConfigObj = { - ortb2Fragments: { - global: { - device: {}, - }, - bidder: { - exampleBidder: { - device: { - ua: 'user-agent', + it('should return true when A/B testing is not configured', () => { + const config = { params: {} }; + expect(wurflSubmodule.init(config)).to.be.true; + }); + + it('should return true for users in treatment group (random < abSplit)', () => { + sandbox.stub(Math, 'random').returns(0.25); // 0.25 < 0.5 = treatment + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 0.5 } }; + expect(wurflSubmodule.init(config)).to.be.true; + }); + + it('should return true for users in control group (random >= abSplit)', () => { + sandbox.stub(Math, 'random').returns(0.75); // 0.75 >= 0.5 = control + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 0.5 } }; + expect(wurflSubmodule.init(config)).to.be.true; + }); + + it('should use default abSplit of 0.5 when not specified', () => { + sandbox.stub(Math, 'random').returns(0.40); // 0.40 < 0.5 = treatment + const config = { params: { abTest: true, abName: 'test_sept' } }; + expect(wurflSubmodule.init(config)).to.be.true; + }); + + it('should handle abSplit of 0 (all control)', () => { + sandbox.stub(Math, 'random').returns(0.01); // split <= 0 = control + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 0 } }; + expect(wurflSubmodule.init(config)).to.be.true; + }); + + it('should handle abSplit of 1 (all treatment)', () => { + sandbox.stub(Math, 'random').returns(0.99); // split >= 1 = treatment + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 1 } }; + expect(wurflSubmodule.init(config)).to.be.true; + }); + + it('should skip enrichment for control group in getBidRequestData', (done) => { + sandbox.stub(Math, 'random').returns(0.75); // Control group + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 0.5 } }; + + // Initialize with A/B test config + wurflSubmodule.init(config); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Control group should not enrich + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.equal({}); + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({}); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should send beacon with ab_name and ab_variant for treatment group', (done) => { + sandbox.stub(Math, 'random').returns(0.25); // Treatment group + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 0.5 } }; + + // Initialize with A/B test config + wurflSubmodule.init(config); + + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] } - } - } - } - }; - const bidderCode = 'exampleBidder'; - const wjsData = { - capability1: 'value1', - capability2: 'value2' - }; + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('ab_name', 'test_sept'); + expect(payload).to.have.property('ab_variant', 'treatment'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should send beacon with ab_name and ab_variant for control group', (done) => { + sandbox.stub(Math, 'random').returns(0.75); // Control group + const config = { params: { abTest: true, abName: 'test_sept', abSplit: 0.5 } }; + + // Initialize with A/B test config + wurflSubmodule.init(config); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); - enrichBidderRequest(reqBidsConfigObj, bidderCode, wjsData); + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({ - exampleBidder: { - device: { - ua: 'user-agent', - ext: { - wurfl: { - capability1: 'value1', - capability2: 'value2' + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] } - } - } - } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('ab_name', 'test_sept'); + expect(payload).to.have.property('ab_variant', 'control'); + expect(payload).to.have.property('enrichment', 'none'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); }); - }); - }); - describe('makeOrtb2DeviceType', function () { - it('should return 1 when wurflData is_mobile and is_phone is true', function () { - const wurflData = { is_mobile: true, is_phone: true, is_tablet: false }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(1); - }); + describe('ABTestManager behavior', () => { + it('should disable A/B test when abTest is false', () => { + const config = { params: { abTest: false } }; + wurflSubmodule.init(config); + // A/B test disabled, so enrichment should proceed normally + expect(wurflSubmodule.init(config)).to.be.true; + }); - it('should return 1 when wurflData is_mobile and is_tablet is true', function () { - const wurflData = { is_mobile: true, is_phone: false, is_tablet: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(1); - }); + it('should assign control group when split is 0', (done) => { + sandbox.stub(Math, 'random').returns(0.01); + const config = { params: { abTest: true, abName: 'test_split', abSplit: 0, abExcludeLCE: false } }; + wurflSubmodule.init(config); - it('should return 6 when wurflData is_mobile but is_phone and is_tablet are false', function () { - const wurflData = { is_mobile: true, is_phone: false, is_tablet: false }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(6); - }); + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); - it('should return 2 when wurflData is_full_desktop is true', function () { - const wurflData = { is_full_desktop: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(2); - }); + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - it('should return 3 when wurflData is_connected_tv is true', function () { - const wurflData = { is_connected_tv: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(3); - }); + const callback = () => { + // Control group should skip enrichment + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.equal({}); + done(); + }; - it('should return 4 when wurflData is_phone is true and is_mobile is false or undefined', function () { - const wurflData = { is_phone: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(4); - }); + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); - it('should return 5 when wurflData is_tablet is true and is_mobile is false or undefined', function () { - const wurflData = { is_tablet: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(5); - }); + it('should assign treatment group when split is 1', (done) => { + sandbox.stub(Math, 'random').returns(0.99); + const config = { params: { abTest: true, abName: 'test_split', abSplit: 1, abExcludeLCE: false } }; + wurflSubmodule.init(config); - it('should return 7 when wurflData is_ott is true', function () { - const wurflData = { is_ott: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.equal(7); - }); + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); - it('should return undefined when wurflData is_mobile is true but is_phone and is_tablet are missing', function () { - const wurflData = { is_mobile: true }; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.be.undefined; - }); + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; - it('should return undefined when no conditions are met', function () { - const wurflData = {}; - const result = makeOrtb2DeviceType(wurflData); - expect(result).to.be.undefined; - }); - }); + const callback = () => { + // Treatment group should enrich + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.not.deep.equal({}); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should use default abName when not provided', (done) => { + sandbox.stub(Math, 'random').returns(0.25); + const config = { params: { abTest: true, abSplit: 0.5, abExcludeLCE: false } }; + wurflSubmodule.init(config); + + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + expect(payload).to.have.property('ab_name', 'unknown'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should exclude LCE from A/B test when abExcludeLCE is true (control group)', (done) => { + sandbox.stub(Math, 'random').returns(0.75); // Control group + const config = { params: { abTest: true, abName: 'test_lce', abSplit: 0.5, abExcludeLCE: true } }; + wurflSubmodule.init(config); + + // Trigger LCE (no cache) + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Control group should still enrich with LCE when excluded + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.have.property('js', 1); + + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + // Beacon should NOT include ab_name and ab_variant when LCE excluded + expect(payload).to.not.have.property('ab_name'); + expect(payload).to.not.have.property('ab_variant'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should exclude LCE from A/B test when abExcludeLCE is true (treatment group)', (done) => { + sandbox.stub(Math, 'random').returns(0.25); // Treatment group + const config = { params: { abTest: true, abName: 'test_lce', abSplit: 0.5, abExcludeLCE: true } }; + wurflSubmodule.init(config); + + // Trigger LCE (no cache) + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Treatment group should enrich with LCE + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.have.property('js', 1); + + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + // Beacon should NOT include ab_name and ab_variant when LCE excluded + expect(payload).to.not.have.property('ab_name'); + expect(payload).to.not.have.property('ab_variant'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should include WURFL in A/B test when abExcludeLCE is true (control group)', (done) => { + sandbox.stub(Math, 'random').returns(0.75); // Control group + const config = { params: { abTest: true, abName: 'test_wurfl', abSplit: 0.5, abExcludeLCE: true } }; + wurflSubmodule.init(config); + + // Provide WURFL cache + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Control group should skip enrichment + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.equal({}); + + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + // Beacon should include ab_name and ab_variant for WURFL + expect(payload).to.have.property('ab_name', 'test_wurfl'); + expect(payload).to.have.property('ab_variant', 'control'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should include LCE in A/B test when abExcludeLCE is false (control group)', (done) => { + sandbox.stub(Math, 'random').returns(0.75); // Control group + const config = { params: { abTest: true, abName: 'test_include_lce', abSplit: 0.5, abExcludeLCE: false } }; + wurflSubmodule.init(config); + + // Trigger LCE (no cache) + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Control group should skip enrichment even with LCE + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.equal({}); + + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + // Beacon should include ab_name and ab_variant + expect(payload).to.have.property('ab_name', 'test_include_lce'); + expect(payload).to.have.property('ab_variant', 'control'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should include LCE in A/B test when abExcludeLCE is false (treatment group)', (done) => { + sandbox.stub(Math, 'random').returns(0.25); // Treatment group + const config = { params: { abTest: true, abName: 'test_include_lce', abSplit: 0.5, abExcludeLCE: false } }; + wurflSubmodule.init(config); + + // Trigger LCE (no cache) + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Treatment group should enrich with LCE + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.have.property('js', 1); + + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + // Beacon should include ab_name and ab_variant + expect(payload).to.have.property('ab_name', 'test_include_lce'); + expect(payload).to.have.property('ab_variant', 'treatment'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should default abExcludeLCE to true', (done) => { + sandbox.stub(Math, 'random').returns(0.75); // Control group + const config = { params: { abTest: true, abName: 'test_default', abSplit: 0.5 } }; // No abExcludeLCE specified + wurflSubmodule.init(config); + + // Trigger LCE (no cache) + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + // Should behave like abExcludeLCE: true (control enriches with LCE) + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.have.property('js', 1); + + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; - describe('toNumber', function () { - it('converts valid numbers', function () { - expect(toNumber(42)).to.equal(42); - expect(toNumber(3.14)).to.equal(3.14); - expect(toNumber('100')).to.equal(100); - expect(toNumber('3.14')).to.equal(3.14); - expect(toNumber(' 50 ')).to.equal(50); + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, null); + + const payload = JSON.parse(sendBeaconStub.getCall(0).args[1]); + // Beacon should NOT include ab_name and ab_variant (default is true) + expect(payload).to.not.have.property('ab_name'); + expect(payload).to.not.have.property('ab_variant'); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + }); }); - it('converts booleans correctly', function () { - expect(toNumber(true)).to.equal(1); - expect(toNumber(false)).to.equal(0); + it('should enrich multiple bidders with cached WURFL data (not over quota)', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup localStorage with cached WURFL data + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = () => { + // Verify global FPD has device data (not over quota) + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4, + os: 'Android', + osv: '6.0', + hwv: 'Nexus 5', + h: 1920, + w: 1080, + ppi: 443, + pxratio: 3.0, + js: 1 + }); + + // Verify global has ext.wurfl with basic+pub capabilities (new behavior) + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).to.exist; + + // Calculate expected basic+pub caps + const basicIndices = wurfl_pbjs.global.basic_set.cap_indices; + const pubIndices = wurfl_pbjs.global.publisher.cap_indices; + const allBasicPubIndices = [...new Set([...basicIndices, ...pubIndices])]; + const expectedBasicPubCaps = {}; + allBasicPubIndices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBasicPubCaps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).to.deep.equal(expectedBasicPubCaps); + + // Under quota, authorized bidders: should get only bidder-specific caps (delta) + const bidder1Indices = wurfl_pbjs.bidders.bidder1.cap_indices; + const expectedBidder1Caps = {}; + bidder1Indices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBidder1Caps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1.device.ext.wurfl).to.deep.equal(expectedBidder1Caps); + + const bidder2Indices = wurfl_pbjs.bidders.bidder2.cap_indices; + const expectedBidder2Caps = {}; + bidder2Indices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBidder2Caps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2.device.ext.wurfl).to.deep.equal(expectedBidder2Caps); + + // bidder3 is NOT authorized, should get empty object (inherits from global) + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder3).to.not.exist; + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); }); - it('handles special cases', function () { - expect(toNumber(null)).to.be.undefined; - expect(toNumber('')).to.be.undefined; + it('should use LCE data when cache is empty and load WURFL.js async', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup empty cache + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + // Set global debug flag + config.setConfig({ debug: true }); + + const expectedURL = new URL(altHost); + expectedURL.searchParams.set('debug', 'true'); + expectedURL.searchParams.set('mode', 'prebid2'); + expectedURL.searchParams.set('bidders', 'bidder1,bidder2,bidder3'); + + const callback = () => { + // Verify global FPD has LCE device data + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.js).to.equal(1); + + // Verify ext.wurfl.is_robot is set + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.false; + + // No bidder enrichment should occur without cached WURFL data + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({}); + + done(); + }; + + const moduleConfig = { + params: { + altHost: altHost, + } + }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, moduleConfig, userConsent); + + // Verify WURFL.js is loaded async for future requests + expect(loadExternalScriptStub.calledOnce).to.be.true; + const loadExternalScriptCall = loadExternalScriptStub.getCall(0); + expect(loadExternalScriptCall.args[0]).to.equal(expectedURL.toString()); + expect(loadExternalScriptCall.args[2]).to.equal('wurfl'); }); - it('returns undefined for non-numeric values', function () { - expect(toNumber('abc')).to.be.undefined; - expect(toNumber(undefined)).to.be.undefined; - expect(toNumber(NaN)).to.be.undefined; - expect(toNumber({})).to.be.undefined; - expect(toNumber([1, 2, 3])).to.be.undefined; - // WURFL.js cannot return [] so it is safe to not handle and return undefined - expect(toNumber([])).to.equal(0); + it('should not include device.w and device.h in LCE enrichment (removed in v2.3.0 - fingerprinting APIs)', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup empty cache to trigger LCE + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + // Mock a typical desktop Chrome user agent to get consistent device detection + const originalUserAgent = navigator.userAgent; + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + configurable: true, + writable: true + }); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + + // Verify device object exists + expect(device).to.exist; + + // CRITICAL: Verify device.w and device.h are NOT present + // These were removed in v2.3.0 due to fingerprinting API concerns (screen.availWidth, screen.width/height) + expect(device).to.not.have.property('w'); + expect(device).to.not.have.property('h'); + + // Verify other ORTB2_DEVICE_FIELDS properties ARE populated when available + // From ORTB2_DEVICE_FIELDS: ['make', 'model', 'devicetype', 'os', 'osv', 'hwv', 'h', 'w', 'ppi', 'pxratio', 'js'] + expect(device.js).to.equal(1); // Always present + + // These should be present based on UA detection + expect(device.make).to.be.a('string').and.not.be.empty; + expect(device.devicetype).to.be.a('number'); // ORTB2_DEVICE_TYPE.PERSONAL_COMPUTER (2) + expect(device.os).to.be.a('string').and.not.be.empty; + + // osv, model, hwv may be present depending on UA + if (device.osv !== undefined) { + expect(device.osv).to.be.a('string'); + } + if (device.model !== undefined) { + expect(device.model).to.be.a('string'); + } + if (device.hwv !== undefined) { + expect(device.hwv).to.be.a('string'); + } + + // pxratio uses OS-based hardcoded values (v2.4.0+), not window.devicePixelRatio (fingerprinting API) + if (device.pxratio !== undefined) { + expect(device.pxratio).to.be.a('number'); + } + + // ppi is not typically populated by LCE (would come from WURFL server-side data) + // Just verify it doesn't exist or is undefined in LCE mode + expect(device.ppi).to.be.undefined; + + // Verify ext.wurfl.is_robot is set + expect(device.ext).to.exist; + expect(device.ext.wurfl).to.exist; + expect(device.ext.wurfl.is_robot).to.be.a('boolean'); + + // Restore original userAgent + Object.defineProperty(navigator, 'userAgent', { + value: originalUserAgent, + configurable: true, + writable: true + }); + + done(); + }; + + const moduleConfig = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, moduleConfig, userConsent); + }); + + describe('LCE bot detection', () => { + let originalUserAgent; + + beforeEach(() => { + // Setup empty cache to trigger LCE + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Save original userAgent + originalUserAgent = navigator.userAgent; + }); + + afterEach(() => { + // Restore original userAgent + Object.defineProperty(navigator, 'userAgent', { + value: originalUserAgent, + configurable: true, + writable: true + }); + }); + + it('should detect Googlebot and set is_robot to true', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', + configurable: true, + writable: true + }); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.true; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should detect BingPreview and set is_robot to true', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b', + configurable: true, + writable: true + }); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.true; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should detect Yahoo! Slurp and set is_robot to true', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)', + configurable: true, + writable: true + }); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.true; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should detect +http bot token and set is_robot to true', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'SomeBot/1.0 (+http://example.com/bot)', + configurable: true, + writable: true + }); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.true; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should set is_robot to false for regular Chrome user agent', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + configurable: true, + writable: true + }); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.false; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should set is_robot to false for regular mobile Safari user agent', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1', + configurable: true, + writable: true + }); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl.is_robot).to.be.false; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + }); + + describe('LCE pxratio (OS-based device pixel ratio)', () => { + let originalUserAgent; + + beforeEach(() => { + // Setup empty cache to trigger LCE + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Save original userAgent + originalUserAgent = navigator.userAgent; + }); + + afterEach(() => { + // Restore original userAgent + Object.defineProperty(navigator, 'userAgent', { + value: originalUserAgent, + configurable: true, + writable: true + }); + }); + + it('should set pxratio to 2.0 for Android devices', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36', + configurable: true, + writable: true + }); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + expect(device.pxratio).to.equal(2.0); + expect(device.os).to.equal('Android'); + expect(device.devicetype).to.equal(4); // PHONE + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should set pxratio to 3.0 for iOS (iPhone) devices', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1', + configurable: true, + writable: true + }); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + expect(device.pxratio).to.equal(3.0); + expect(device.os).to.equal('iOS'); + expect(device.devicetype).to.equal(4); // PHONE + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should set pxratio to 2.0 for iPadOS (iPad) devices', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1', + configurable: true, + writable: true + }); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + expect(device.pxratio).to.equal(2.0); + expect(device.os).to.equal('iPadOS'); + expect(device.devicetype).to.equal(5); // TABLET + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should set pxratio to 1.0 for desktop/other devices (default)', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + configurable: true, + writable: true + }); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + expect(device.pxratio).to.equal(1.0); + expect(device.os).to.equal('Windows'); + expect(device.devicetype).to.equal(2); // PERSONAL_COMPUTER + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should set pxratio to 1.0 for macOS devices (default)', (done) => { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + configurable: true, + writable: true + }); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + expect(device.pxratio).to.equal(1.0); + expect(device.os).to.equal('macOS'); + expect(device.devicetype).to.equal(2); // PERSONAL_COMPUTER + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + }); + + it('should enrich only bidders when over quota', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup localStorage with cached WURFL data (over quota) + const wurfl_pbjs_over_quota = { + ...wurfl_pbjs, + over_quota: 1 + }; + const cachedData = { WURFL, wurfl_pbjs: wurfl_pbjs_over_quota }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = () => { + // Verify global FPD does NOT have device data (over quota) + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.equal({}); + + // Over quota, authorized bidders: should get basic + bidder-specific (NO pub) + // bidder1 should get device fields + ext.wurfl with basic + bidder1-specific + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4, + os: 'Android', + osv: '6.0', + hwv: 'Nexus 5', + h: 1920, + w: 1080, + ppi: 443, + pxratio: 3.0, + js: 1 + }); + const basicIndices = wurfl_pbjs_over_quota.global.basic_set.cap_indices; + const bidder1Indices = wurfl_pbjs_over_quota.bidders.bidder1.cap_indices; + const allBidder1Indices = [...new Set([...basicIndices, ...bidder1Indices])]; + const expectedBidder1AllCaps = {}; + allBidder1Indices.forEach(index => { + const capName = wurfl_pbjs_over_quota.caps[index]; + if (capName && capName in WURFL) { + expectedBidder1AllCaps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1.device.ext.wurfl).to.deep.equal(expectedBidder1AllCaps); + + // bidder2 should get device fields + ext.wurfl with basic + bidder2-specific + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4, + os: 'Android', + osv: '6.0', + hwv: 'Nexus 5', + h: 1920, + w: 1080, + ppi: 443, + pxratio: 3.0, + js: 1 + }); + const bidder2Indices = wurfl_pbjs_over_quota.bidders.bidder2.cap_indices; + const allBidder2Indices = [...new Set([...basicIndices, ...bidder2Indices])]; + const expectedBidder2AllCaps = {}; + allBidder2Indices.forEach(index => { + const capName = wurfl_pbjs_over_quota.caps[index]; + if (capName && capName in WURFL) { + expectedBidder2AllCaps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2.device.ext.wurfl).to.deep.equal(expectedBidder2AllCaps); + + // bidder3 is NOT authorized, should get nothing + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder3).to.be.undefined; + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + + it('should initialize ortb2Fragments.bidder when undefined and enrich authorized bidders (over quota)', (done) => { + // Test the fix for ortb2Fragments.bidder being undefined + reqBidsConfigObj.ortb2Fragments.global.device = {}; + // Explicitly set bidder to undefined to simulate the race condition + reqBidsConfigObj.ortb2Fragments.bidder = undefined; + + // Setup localStorage with cached WURFL data (over quota) + const wurfl_pbjs_over_quota = { + ...wurfl_pbjs, + over_quota: 1 + }; + const cachedData = { WURFL, wurfl_pbjs: wurfl_pbjs_over_quota }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = () => { + // Verify ortb2Fragments.bidder was properly initialized + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.be.an('object'); + + // Verify global FPD does NOT have device data (over quota) + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.equal({}); + + // Over quota, authorized bidders: should get basic + pub + bidder-specific caps (ALL) + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4, + os: 'Android', + osv: '6.0', + hwv: 'Nexus 5', + h: 1920, + w: 1080, + ppi: 443, + pxratio: 3.0, + js: 1 + }); + const basicIndices = wurfl_pbjs_over_quota.global.basic_set.cap_indices; + const bidder1Indices = wurfl_pbjs_over_quota.bidders.bidder1.cap_indices; + const allBidder1Indices = [...new Set([...basicIndices, ...bidder1Indices])]; + const expectedBidder1AllCaps = {}; + allBidder1Indices.forEach(index => { + const capName = wurfl_pbjs_over_quota.caps[index]; + if (capName && capName in WURFL) { + expectedBidder1AllCaps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1.device.ext.wurfl).to.deep.equal(expectedBidder1AllCaps); + + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4 + }); + const bidder2Indices = wurfl_pbjs_over_quota.bidders.bidder2.cap_indices; + const allBidder2Indices = [...new Set([...basicIndices, ...bidder2Indices])]; + const expectedBidder2AllCaps = {}; + allBidder2Indices.forEach(index => { + const capName = wurfl_pbjs_over_quota.caps[index]; + if (capName && capName in WURFL) { + expectedBidder2AllCaps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2.device.ext.wurfl).to.deep.equal(expectedBidder2AllCaps); + + // bidder3 is NOT authorized, should get nothing + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder3).to.be.undefined; + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + + it('should work with guardOrtb2Fragments Proxy (Prebid 10.x compatibility)', (done) => { + // Simulate Prebid 10.x where rtdModule wraps ortb2Fragments with guardOrtb2Fragments + const plainFragments = { + global: { device: {} }, + bidder: {} + }; + + const plainReqBidsConfigObj = { + adUnits: [{ + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' } + ] + }], + ortb2Fragments: plainFragments + }; + + // Setup localStorage with cached WURFL data (over quota) + const wurfl_pbjs_over_quota = { + ...wurfl_pbjs, + over_quota: 1 + }; + const cachedData = { WURFL, wurfl_pbjs: wurfl_pbjs_over_quota }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + // Wrap with guard (like rtdModule does in production) + const guardedFragments = guardOrtb2Fragments(plainFragments, {}); + const guardedReqBidsConfigObj = { ...plainReqBidsConfigObj, ortb2Fragments: guardedFragments }; + + const callback = () => { + // Over quota, authorized bidders: should get basic + pub + bidder-specific caps (ALL) + expect(plainFragments.bidder.bidder1).to.exist; + expect(plainFragments.bidder.bidder1.device).to.exist; + expect(plainFragments.bidder.bidder1.device.ext).to.exist; + + const basicIndices = wurfl_pbjs_over_quota.global.basic_set.cap_indices; + const bidder1Indices = wurfl_pbjs_over_quota.bidders.bidder1.cap_indices; + const allBidder1Indices = [...new Set([...basicIndices, ...bidder1Indices])]; + const expectedBidder1AllCaps = {}; + allBidder1Indices.forEach(index => { + const capName = wurfl_pbjs_over_quota.caps[index]; + if (capName && capName in WURFL) { + expectedBidder1AllCaps[capName] = WURFL[capName]; + } + }); + expect(plainFragments.bidder.bidder1.device.ext.wurfl).to.deep.equal(expectedBidder1AllCaps); + + // Verify FPD is present + expect(plainFragments.bidder.bidder1.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4, + os: 'Android', + osv: '6.0' + }); + + // Verify bidder2 (authorized) also got enriched + expect(plainFragments.bidder.bidder2).to.exist; + const bidder2Indices = wurfl_pbjs_over_quota.bidders.bidder2.cap_indices; + const allBidder2Indices = [...new Set([...basicIndices, ...bidder2Indices])]; + const expectedBidder2AllCaps = {}; + allBidder2Indices.forEach(index => { + const capName = wurfl_pbjs_over_quota.caps[index]; + if (capName && capName in WURFL) { + expectedBidder2AllCaps[capName] = WURFL[capName]; + } + }); + expect(plainFragments.bidder.bidder2.device.ext.wurfl).to.deep.equal(expectedBidder2AllCaps); + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(guardedReqBidsConfigObj, callback, config, userConsent); + }); + + it('should pass basic+pub caps via global and authorized bidders get full caps when under quota', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup localStorage with cached WURFL data (NOT over quota) + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = () => { + // Verify global FPD has device data (not over quota) + expect(reqBidsConfigObj.ortb2Fragments.global.device).to.deep.include({ + make: 'Google', + model: 'Nexus 5', + devicetype: 4 + }); + + // Calculate expected caps for basic + pub (no bidder-specific) + const basicIndices = wurfl_pbjs.global.basic_set.cap_indices; + const pubIndices = wurfl_pbjs.global.publisher.cap_indices; + const allBasicPubIndices = [...new Set([...basicIndices, ...pubIndices])]; + + const expectedBasicPubCaps = {}; + allBasicPubIndices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBasicPubCaps[capName] = WURFL[capName]; + } + }); + + // Verify global has ext.wurfl with basic+pub caps (new behavior) + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).to.deep.equal(expectedBasicPubCaps); + + // Under quota, authorized bidders: should get only bidder-specific caps (delta) + const bidder1Indices = wurfl_pbjs.bidders.bidder1.cap_indices; + const expectedBidder1Caps = {}; + bidder1Indices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBidder1Caps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder1.device.ext.wurfl).to.deep.equal(expectedBidder1Caps); + + const bidder2Indices = wurfl_pbjs.bidders.bidder2.cap_indices; + const expectedBidder2Caps = {}; + bidder2Indices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBidder2Caps[capName] = WURFL[capName]; + } + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder2.device.ext.wurfl).to.deep.equal(expectedBidder2Caps); + + // bidder3 is NOT authorized, should get NOTHING (inherits from global.device.ext.wurfl) + expect(reqBidsConfigObj.ortb2Fragments.bidder.bidder3).to.not.exist; + + // Verify the caps calculation: basic+pub union in global + const globalCapCount = Object.keys(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).length; + expect(globalCapCount).to.equal(allBasicPubIndices.length); + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + + it('should enrich global.device.ext.wurfl when under quota (verifies GlobalExt)', (done) => { + // This test verifies that GlobalExt() is called and global enrichment works + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const callback = () => { + // Calculate expected basic+pub caps + const basicIndices = wurfl_pbjs.global.basic_set.cap_indices; + const pubIndices = wurfl_pbjs.global.publisher.cap_indices; + const allBasicPubIndices = [...new Set([...basicIndices, ...pubIndices])]; + const expectedBasicPubCaps = {}; + allBasicPubIndices.forEach(index => { + const capName = wurfl_pbjs.caps[index]; + if (capName && capName in WURFL) { + expectedBasicPubCaps[capName] = WURFL[capName]; + } + }); + + // Verify GlobalExt() populated global.device.ext.wurfl with basic+pub + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext).to.exist; + expect(reqBidsConfigObj.ortb2Fragments.global.device.ext.wurfl).to.deep.equal(expectedBasicPubCaps); + + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('onAuctionEndEvent: should send analytics data using navigator.sendBeacon, if available', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup localStorage with cached WURFL data to populate enrichedBidders + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon').returns(true); + + // Mock getGlobal().getHighestCpmBids() + const mockHighestCpmBids = [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1' } + ]; + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => mockHighestCpmBids + }); + + const callback = () => { + // Build auctionDetails with bidsReceived and adUnits + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' }, + { requestId: 'req2', bidderCode: 'bidder2', adUnitCode: 'ad1', cpm: 1.2, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' } + ] + } + ] + }; + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Assertions + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + expect(beaconCall.args[0]).to.equal(expectedStatsURL); + + // Parse and verify payload structure + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('version'); + expect(payload).to.have.property('domain'); + expect(payload).to.have.property('path'); + expect(payload).to.have.property('sampling_rate', 100); + expect(payload).to.have.property('enrichment', 'wurfl_pub'); + expect(payload).to.have.property('wurfl_id', 'lg_nexus5_ver1'); + expect(payload).to.have.property('over_quota', 0); + expect(payload).to.have.property('consent_class', 0); + expect(payload).to.have.property('ad_units'); + expect(payload.ad_units).to.be.an('array').with.lengthOf(1); + expect(payload.ad_units[0].ad_unit_code).to.equal('ad1'); + expect(payload.ad_units[0].bidders).to.be.an('array').with.lengthOf(2); + expect(payload.ad_units[0].bidders[0]).to.deep.include({ + bidder: 'bidder1', + bdr_enrich: 'wurfl_ssp', + cpm: 1.5, + currency: 'USD', + won: true + }); + expect(payload.ad_units[0].bidders[1]).to.deep.include({ + bidder: 'bidder2', + bdr_enrich: 'wurfl_ssp', + cpm: 1.2, + currency: 'USD', + won: false + }); + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + // First enrich bidders to populate enrichedBidders Set + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + + it('onAuctionEndEvent: should send analytics data using fetch as fallback, if navigator.sendBeacon is not available', (done) => { + // Reset reqBidsConfigObj to clean state + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + // Setup localStorage with cached WURFL data to populate enrichedBidders + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(false); + const fetchAjaxStub = sandbox.stub(ajaxModule, 'fetch').returns(Promise.resolve()); + + // Mock getGlobal().getHighestCpmBids() + const mockHighestCpmBids = [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1' } + ]; + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => mockHighestCpmBids + }); + + const callback = () => { + // Build auctionDetails with bidsReceived and adUnits + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' }, + { requestId: 'req2', bidderCode: 'bidder2', adUnitCode: 'ad1', cpm: 1.2, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' } + ] + } + ] + }; + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Assertions + expect(sendBeaconStub.calledOnce).to.be.true; + + expect(fetchAjaxStub.calledOnce).to.be.true; + const fetchAjaxCall = fetchAjaxStub.getCall(0); + expect(fetchAjaxCall.args[0]).to.equal(expectedStatsURL); + expect(fetchAjaxCall.args[1].method).to.equal('POST'); + expect(fetchAjaxCall.args[1].mode).to.equal('no-cors'); + + // Parse and verify payload structure + const payload = JSON.parse(fetchAjaxCall.args[1].body); + expect(payload).to.have.property('domain'); + expect(payload).to.have.property('path'); + expect(payload).to.have.property('sampling_rate', 100); + expect(payload).to.have.property('enrichment', 'wurfl_pub'); + expect(payload).to.have.property('wurfl_id', 'lg_nexus5_ver1'); + expect(payload).to.have.property('over_quota', 0); + expect(payload).to.have.property('consent_class', 0); + expect(payload).to.have.property('ad_units'); + expect(payload.ad_units).to.be.an('array').with.lengthOf(1); + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + // First enrich bidders to populate enrichedBidders Set + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + + describe('consent classification', () => { + beforeEach(function () { + // Setup localStorage with cached WURFL data + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + // Mock getGlobal().getHighestCpmBids() + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + // Reset reqBidsConfigObj + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + }); + + const testConsentClass = (description, userConsent, expectedClass, done) => { + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + const config = { params: {} }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('consent_class', expectedClass); + done(); + }; + + const config = { params: {} }; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }; + + it('should return NO consent (0) when userConsent is null', (done) => { + testConsentClass('null userConsent', null, 0, done); + }); + + it('should return NO consent (0) when userConsent is empty object', (done) => { + testConsentClass('empty object', {}, 0, done); + }); + + it('should return NO consent (0) when COPPA is enabled', (done) => { + testConsentClass('COPPA enabled', { coppa: true }, 0, done); + }); + + it('should return NO consent (0) when USP opt-out (1Y)', (done) => { + testConsentClass('USP opt-out', { usp: '1YYN' }, 0, done); + }); + + it('should return NO consent (0) when GDPR applies but no purposes granted', (done) => { + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: {}, + legitimateInterests: {} + } + } + } + }; + testConsentClass('GDPR no purposes', userConsent, 0, done); + }); + + it('should return FULL consent (2) when no GDPR object (non-GDPR region)', (done) => { + testConsentClass('no GDPR object', { usp: '1NNN' }, 2, done); + }); + + it('should return FULL consent (2) when GDPR does not apply', (done) => { + const userConsent = { + gdpr: { + gdprApplies: false + } + }; + testConsentClass('GDPR not applicable', userConsent, 2, done); + }); + + it('should return FULL consent (2) when all 3 GDPR purposes granted via consents', (done) => { + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { 7: true, 8: true, 10: true } + } + } + } + }; + testConsentClass('all purposes via consents', userConsent, 2, done); + }); + + it('should return FULL consent (2) when all 3 GDPR purposes granted via legitimateInterests', (done) => { + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + legitimateInterests: { 7: true, 8: true, 10: true } + } + } + } + }; + testConsentClass('all purposes via LI', userConsent, 2, done); + }); + + it('should return FULL consent (2) when all 3 GDPR purposes granted via mixed consents and LI', (done) => { + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { 7: true, 10: true }, + legitimateInterests: { 8: true } + } + } + } + }; + testConsentClass('mixed consents and LI', userConsent, 2, done); + }); + + it('should return PARTIAL consent (1) when only 1 GDPR purpose granted', (done) => { + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { 7: true } + } + } + } + }; + testConsentClass('1 purpose granted', userConsent, 1, done); + }); + + it('should return PARTIAL consent (1) when 2 GDPR purposes granted', (done) => { + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { 7: true }, + legitimateInterests: { 8: true } + } + } + } + }; + testConsentClass('2 purposes granted', userConsent, 1, done); + }); + }); + + describe('sampling rate', () => { + it('should not send beacon when sampling_rate is 0', (done) => { + // Setup WURFL data with sampling_rate: 0 + const wurfl_pbjs_zero_sampling = { ...wurfl_pbjs, sampling_rate: 0 }; + const cachedData = { WURFL, wurfl_pbjs: wurfl_pbjs_zero_sampling }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon'); + const fetchStub = sandbox.stub(ajaxModule, 'fetch').returns(Promise.resolve()); + + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + const config = { params: {} }; + const userConsent = null; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Beacon should NOT be sent due to sampling_rate: 0 + expect(sendBeaconStub.called).to.be.false; + expect(fetchStub.called).to.be.false; + done(); + }; + + const config = { params: {} }; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should send beacon when sampling_rate is 100', (done) => { + // Setup WURFL data with sampling_rate: 100 + const wurfl_pbjs_full_sampling = { ...wurfl_pbjs, sampling_rate: 100 }; + const cachedData = { WURFL, wurfl_pbjs: wurfl_pbjs_full_sampling }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + const config = { params: {} }; + const userConsent = null; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Beacon should be sent + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('sampling_rate', 100); + done(); + }; + + const config = { params: {} }; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + + it('should use default sampling_rate (100) for LCE and send beacon', (done) => { + // No cached data - will use LCE + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + const config = { params: {} }; + const userConsent = null; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Beacon should be sent with default sampling_rate + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('sampling_rate', 100); + // Enrichment type can be 'lce' or 'lcefailed' depending on what data is available + expect(payload.enrichment).to.be.oneOf(['lce', 'lcefailed']); + done(); + }; + + const config = { params: {} }; + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, {}); + }); + }); + + describe('onAuctionEndEvent: overquota beacon enrichment', () => { + beforeEach(() => { + // Mock getGlobal().getHighestCpmBids() + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + // Reset reqBidsConfigObj + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + }); + + it('should report wurfl_ssp for authorized bidders and none for unauthorized when overquota', (done) => { + // Setup overquota scenario + const wurfl_pbjs_over_quota = { + ...wurfl_pbjs, + over_quota: 1 + }; + const cachedData = { WURFL, wurfl_pbjs: wurfl_pbjs_over_quota }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' }, + { requestId: 'req2', bidderCode: 'bidder2', adUnitCode: 'ad1', cpm: 1.2, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [ + { bidder: 'bidder1' }, // authorized + { bidder: 'bidder2' }, // authorized + { bidder: 'bidder3' } // NOT authorized + ] + } + ] + }; + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + + // Verify overall enrichment is none when overquota (publisher not enriched) + expect(payload).to.have.property('enrichment', 'none'); + expect(payload).to.have.property('over_quota', 1); + + // Verify per-bidder enrichment + expect(payload.ad_units).to.be.an('array').with.lengthOf(1); + expect(payload.ad_units[0].bidders).to.be.an('array').with.lengthOf(3); + + // bidder1 and bidder2 are authorized - should report wurfl_ssp + expect(payload.ad_units[0].bidders[0]).to.deep.include({ + bidder: 'bidder1', + bdr_enrich: 'wurfl_ssp', + cpm: 1.5, + currency: 'USD', + won: false + }); + expect(payload.ad_units[0].bidders[1]).to.deep.include({ + bidder: 'bidder2', + bdr_enrich: 'wurfl_ssp', + cpm: 1.2, + currency: 'USD', + won: false + }); + + // bidder3 is NOT authorized and overquota - should report none + expect(payload.ad_units[0].bidders[2]).to.deep.include({ + bidder: 'bidder3', + bdr_enrich: 'none', + won: false + }); + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + + it('should report wurfl_ssp for authorized and wurfl_pub for unauthorized when not overquota', (done) => { + // Setup NOT overquota scenario + const cachedData = { WURFL, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + const callback = () => { + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' }, + { requestId: 'req3', bidderCode: 'bidder3', adUnitCode: 'ad1', cpm: 1.0, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [ + { bidder: 'bidder1' }, // authorized + { bidder: 'bidder3' } // NOT authorized + ] + } + ] + }; + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + + // Verify overall enrichment is wurfl_pub when not overquota + expect(payload).to.have.property('enrichment', 'wurfl_pub'); + expect(payload).to.have.property('over_quota', 0); + + // Verify per-bidder enrichment + expect(payload.ad_units).to.be.an('array').with.lengthOf(1); + expect(payload.ad_units[0].bidders).to.be.an('array').with.lengthOf(2); + + // bidder1 is authorized - should always report wurfl_ssp + expect(payload.ad_units[0].bidders[0]).to.deep.include({ + bidder: 'bidder1', + bdr_enrich: 'wurfl_ssp', + cpm: 1.5, + currency: 'USD', + won: false + }); + + // bidder3 is NOT authorized but not overquota - should report wurfl_pub + expect(payload.ad_units[0].bidders[1]).to.deep.include({ + bidder: 'bidder3', + bdr_enrich: 'wurfl_pub', + cpm: 1.0, + currency: 'USD', + won: false + }); + + done(); + }; + + const config = { params: {} }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + }); + }); + + describe('device type mapping', () => { + it('should map is_ott priority over form_factor', (done) => { + const wurflWithOtt = { ...WURFL, is_ott: true, form_factor: 'Desktop' }; + const cachedData = { WURFL: wurflWithOtt, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(7); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map is_console priority over form_factor', (done) => { + const wurflWithConsole = { ...WURFL, is_console: true, form_factor: 'Desktop' }; + const cachedData = { WURFL: wurflWithConsole, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(6); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map physical_form_factor out_of_home_device', (done) => { + const wurflWithOOH = { ...WURFL, physical_form_factor: 'out_of_home_device', form_factor: 'Desktop' }; + const cachedData = { WURFL: wurflWithOOH, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(8); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map form_factor Desktop to PERSONAL_COMPUTER', (done) => { + const wurflDesktop = { ...WURFL, form_factor: 'Desktop' }; + const cachedData = { WURFL: wurflDesktop, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(2); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map form_factor Smartphone to PHONE', (done) => { + const wurflSmartphone = { ...WURFL, form_factor: 'Smartphone' }; + const cachedData = { WURFL: wurflSmartphone, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(4); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map form_factor Tablet to TABLET', (done) => { + const wurflTablet = { ...WURFL, form_factor: 'Tablet' }; + const cachedData = { WURFL: wurflTablet, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(5); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map form_factor Smart-TV to CONNECTED_TV', (done) => { + const wurflSmartTV = { ...WURFL, form_factor: 'Smart-TV' }; + const cachedData = { WURFL: wurflSmartTV, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(3); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map form_factor Other Non-Mobile to CONNECTED_DEVICE', (done) => { + const wurflOtherNonMobile = { ...WURFL, form_factor: 'Other Non-Mobile' }; + const cachedData = { WURFL: wurflOtherNonMobile, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(6); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should map form_factor Other Mobile to MOBILE_OR_TABLET', (done) => { + const wurflOtherMobile = { ...WURFL, form_factor: 'Other Mobile' }; + const cachedData = { WURFL: wurflOtherMobile, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.equal(1); + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should return undefined when form_factor is missing', (done) => { + const wurflNoFormFactor = { ...WURFL }; + delete wurflNoFormFactor.form_factor; + const cachedData = { WURFL: wurflNoFormFactor, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.be.undefined; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + + it('should return undefined for unknown form_factor', (done) => { + const wurflUnknownFormFactor = { ...WURFL, form_factor: 'UnknownDevice' }; + const cachedData = { WURFL: wurflUnknownFormFactor, wurfl_pbjs }; + sandbox.stub(storage, 'getDataFromLocalStorage').returns(JSON.stringify(cachedData)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.global.device.devicetype).to.be.undefined; + done(); + }; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + }); + }); + + describe('LCE Error Handling', function () { + beforeEach(function () { + // Setup empty cache to force LCE + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'hasLocalStorage').returns(true); + + reqBidsConfigObj.ortb2Fragments.global.device = {}; + reqBidsConfigObj.ortb2Fragments.bidder = {}; + }); + + it('should set LCE_ERROR enrichment type when LCE device detection throws error', (done) => { + const sendBeaconStub = sandbox.stub(ajaxModule, 'sendBeacon').returns(true); + + sandbox.stub(prebidGlobalModule, 'getGlobal').returns({ + getHighestCpmBids: () => [] + }); + + // Import the WurflLCEDevice to stub it + const wurflRtdProvider = require('modules/wurflRtdProvider.js'); + + const callback = () => { + const device = reqBidsConfigObj.ortb2Fragments.global.device; + + // Should have minimal fallback data + expect(device.js).to.equal(1); + + // UA-dependent fields should not be set when error occurs + expect(device.devicetype).to.be.undefined; + expect(device.os).to.be.undefined; + + // Trigger auction to verify enrichment type in beacon + const auctionDetails = { + bidsReceived: [ + { requestId: 'req1', bidderCode: 'bidder1', adUnitCode: 'ad1', cpm: 1.5, currency: 'USD' } + ], + adUnits: [ + { + code: 'ad1', + bids: [{ bidder: 'bidder1' }] + } + ] + }; + + wurflSubmodule.onAuctionEndEvent(auctionDetails, { params: {} }, null); + + // Check beacon was sent with lcefailed enrichment type + expect(sendBeaconStub.calledOnce).to.be.true; + const beaconCall = sendBeaconStub.getCall(0); + const payload = JSON.parse(beaconCall.args[1]); + expect(payload).to.have.property('enrichment', 'lcefailed'); + + done(); + }; + + // Stub _getDeviceInfo to throw an error + const originalGetDeviceInfo = window.navigator.userAgent; + Object.defineProperty(window.navigator, 'userAgent', { + get: () => { + throw new Error('User agent access failed'); + }, + configurable: true + }); + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, { params: {} }, {}); + + // Restore + Object.defineProperty(window.navigator, 'userAgent', { + value: originalGetDeviceInfo, + configurable: true + }); + }); }); }); }); diff --git a/test/spec/modules/yahooAdsBidAdapter_spec.js b/test/spec/modules/yahooAdsBidAdapter_spec.js index ab4f3eb5b8e..093df9355f1 100644 --- a/test/spec/modules/yahooAdsBidAdapter_spec.js +++ b/test/spec/modules/yahooAdsBidAdapter_spec.js @@ -694,7 +694,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; const user = data.user; expect(user[param]).to.be.a('object'); - expect(user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); + // Properties from ortb2.user.ext should be merged into user.ext, not nested + expect(user[param]).to.be.deep.include({a: '123', b: '456'}); }); }); @@ -991,7 +992,6 @@ describe('Yahoo Advertising Bid Adapter:', () => { {source: 'neustar.biz', uids: [{id: 'fabrickId_FROM_USER_ID_MODULE', atype: 1}]} ]; const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user.ext.eids).to.deep.equal(validBidRequests[0].userIdAsEids); }); @@ -1033,6 +1033,7 @@ describe('Yahoo Advertising Bid Adapter:', () => { }); expect(data.source).to.deep.equal({ + tid: undefined, ext: { hb: 1, adapterver: ADAPTER_VERSION, @@ -1055,6 +1056,74 @@ describe('Yahoo Advertising Bid Adapter:', () => { expect(data.cur).to.deep.equal(['USD']); }); + it('should not include source.tid when publisher does not provide it', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.be.undefined; + }); + + it('should include source.tid from bidderRequest.ortb2.source.tid when provided', () => { + const ortb2 = { + source: { + tid: 'test-transaction-id-12345' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.equal('test-transaction-id-12345'); + }); + + it('should read source.tid from global ortb2 config when enableTids is true', () => { + const ortb2 = { + source: { + tid: 'global-tid-67890' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.equal('global-tid-67890'); + expect(data.source.ext.hb).to.equal(1); + expect(data.source.fd).to.equal(1); + }); + + it('should include source.tid alongside existing source.ext properties', () => { + const ortb2 = { + source: { + tid: 'test-tid-with-ext' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + expect(data.source).to.have.property('tid'); + expect(data.source.tid).to.equal('test-tid-with-ext'); + expect(data.source.ext).to.deep.equal({ + hb: 1, + adapterver: ADAPTER_VERSION, + prebidver: PREBID_VERSION, + integration: { + name: INTEGRATION_METHOD, + ver: PREBID_VERSION + } + }); + expect(data.source.fd).to.equal(1); + }); + + it('should handle empty source.tid gracefully', () => { + const ortb2 = { + source: { + tid: '' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.equal(''); + }); + it('should generate a valid openRTB imp.ext object in the bid-request', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); const bid = validBidRequests[0]; diff --git a/test/spec/modules/yaleoBidAdapter_spec.js b/test/spec/modules/yaleoBidAdapter_spec.js new file mode 100644 index 00000000000..5bbba97109c --- /dev/null +++ b/test/spec/modules/yaleoBidAdapter_spec.js @@ -0,0 +1,213 @@ +import { cloneDeep } from "lodash"; +import { spec } from "../../../modules/yaleoBidAdapter.ts"; + +const bannerBidRequestBase = { + adUnitCode: 'banner-ad-unit-code', + auctionId: 'banner-auction-id', + bidId: 'banner-bid-id', + bidder: 'yaleo', + bidderRequestId: 'banner-bidder-request-id', + mediaTypes: { banner: [[300, 250]] }, + params: { placementId: '12345' }, +}; + +const bannerServerResponseBase = { + body: { + id: 'banner-server-response-id', + seatbid: [ + { + bid: [ + { + id: 'banner-seatbid-bid-id', + impid: 'banner-bid-id', + price: 0.05, + adm: '
banner-ad
', + adid: 'banner-ad-id', + adomain: ['audienzz.com', 'yaleo.com'], + iurl: 'iurl', + cid: 'banner-campaign-id', + crid: 'banner-creative-id', + cat: [], + w: 300, + h: 250, + mtype: 1, + }, + ], + seat: 'yaleo', + }, + ], + cur: 'USD', + }, +}; + +describe('Yaleo bid adapter', () => { + let bannerBidRequest; + + beforeEach(() => { + bannerBidRequest = cloneDeep(bannerBidRequestBase); + }); + + describe('spec', () => { + it('checks that spec has required properties', () => { + expect(spec).to.have.property('code', 'yaleo'); + expect(spec).to.have.property('supportedMediaTypes').that.includes('banner'); + expect(spec).to.have.property('isBidRequestValid').that.is.a('function'); + expect(spec).to.have.property('buildRequests').that.is.a('function'); + expect(spec).to.have.property('interpretResponse').that.is.a('function'); + expect(spec).to.have.property('gvlid').that.equals(783); + }); + }); + + describe('isBidRequestValid', () => { + it('returns true when all params are specified', () => { + bannerBidRequest.params = { + placementId: '12345', + maxCpm: 5.00, + memberId: 12345, + }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.true; + }); + + it('returns true when required params are specified', () => { + bannerBidRequest.params = { placementId: '12345' }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.true; + }); + + it('returns false when params are empty', () => { + bannerBidRequest.params = {}; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when params are not specified', () => { + bannerBidRequest.params = undefined; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returnsfalse when required params are not specified', () => { + bannerBidRequest.params = { wrongParam: '12345' }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is a number', () => { + bannerBidRequest.params = { placementId: 12345 }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is a boolean', () => { + bannerBidRequest.params = { placementId: true }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is an object', () => { + bannerBidRequest.params = { placementId: {} }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is an array', () => { + bannerBidRequest.params = { placementId: [] }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is undefined', () => { + bannerBidRequest.params = { placementId: undefined }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is null', () => { + bannerBidRequest.params = { placementId: null }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + }); + + describe('buildRequests', () => { + it('creates a valid banner bid request', () => { + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + ortb2: { + site: { + page: 'http://example.com', + } + } + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bidder.yaleo.com/prebid'); + expect(request.data.imp.length).to.equal(1); + expect(request.data.imp[0]).to.have.property('banner'); + expect(request.data.site.page).to.be.equal('http://example.com'); + }); + + it('checks that all params are passed to the request', () => { + bannerBidRequest.params = { + placementId: '12345', + maxCpm: 5.00, + memberId: 12345, + }; + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const yaleoParams = request.data.imp[0].ext.prebid.bidder.yaleo; + + expect(yaleoParams.placementId).to.equal('12345'); + expect(yaleoParams.maxCpm).to.equal(5.00); + expect(yaleoParams.memberId).to.equal(12345); + }); + }); + + it('checks that only specified params are passed to the request', () => { + bannerBidRequest.params = { + placementId: '12345', + }; + + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const yaleoParams = request.data.imp[0].ext.prebid.bidder.yaleo; + + expect(yaleoParams).to.deep.equal({ placementId: '12345' }); + }); + + describe('interpretResponse', () => { + let bannerServerResponse; + beforeEach(() => { + bannerServerResponse = cloneDeep(bannerServerResponseBase); + }); + + it('parses banner bid response correctly', () => { + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const response = spec.interpretResponse(bannerServerResponse, request); + + expect(response).to.have.property('bids'); + expect(response.bids.length).to.be.equal(1); + expect(response.bids[0].cpm).to.equal(0.05); + expect(response.bids[0].currency).to.equal('USD'); + expect(response.bids[0].mediaType).to.equal('banner'); + expect(response.bids[0].width).to.equal(300); + expect(response.bids[0].height).to.equal(250); + expect(response.bids[0].creativeId).to.equal('banner-creative-id'); + expect(response.bids[0].ad).to.equal('
banner-ad
'); + expect(response.bids[0].bidderCode).to.equal('yaleo'); + expect(response.bids[0].meta.advertiserDomains).to.deep.equal(['audienzz.com', 'yaleo.com']); + expect(response.bids[0].netRevenue).to.be.true; + expect(response.bids[0].ttl).to.equal(300); + }); + }); +}); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 4f4454c17ae..dc4651d2e1f 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -533,11 +533,6 @@ describe('YieldmoAdapter', function () { expect(utils.deepAccess(videoBid, 'params.video')['plcmt']).to.equal(1); }); - it('should add start delay if plcmt value is not 1', function () { - const videoBid = mockVideoBid({}, {}, { plcmt: 2 }); - expect(build([videoBid])[0].data.imp[0].video.startdelay).to.equal(0); - }); - it('should override mediaTypes.video.mimes prop if params.video.mimes is present', function () { utils.deepAccess(videoBid, 'mediaTypes.video')['mimes'] = ['video/mp4']; utils.deepAccess(videoBid, 'params.video')['mimes'] = ['video/mkv']; diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index ea9ca43edbf..3bafe1b83bb 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -548,6 +548,52 @@ describe('yieldoneBidAdapter', function () { expect(request[0].data.gpid).to.equal('gpid_sample'); }); }); + + describe('instl', function () { + it('dont send instl if undefined', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + ortb2Imp: {}, + }, + { + params: {placementId: '2'}, + ortb2Imp: undefined, + }, + { + params: {placementId: '3'}, + ortb2Imp: {instl: undefined}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + bidRequests.forEach((bidRequest, ind) => { + expect(request[ind].data).to.not.have.property('instl'); + }) + }); + + it('should send instl if available', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + ortb2Imp: {instl: '1'}, + }, + { + params: {placementId: '1'}, + ortb2Imp: {instl: 1}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + bidRequests.forEach((bidRequest, ind) => { + expect(request[ind].data).to.have.property('instl'); + expect(request[ind].data.instl).to.equal(1); + }) + }); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 6e55083338f..d3e6e01c655 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -1,8 +1,7 @@ import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; -import {config} from 'src/config'; -import {EVENTS} from 'src/constants.js'; -import {server} from '../../mocks/xhr.js'; -import {logError} from '../../../src/utils.js'; +import { config } from 'src/config'; +import { EVENTS } from 'src/constants.js'; +import { server } from '../../mocks/xhr.js'; const utils = require('src/utils'); const events = require('src/events'); @@ -112,6 +111,9 @@ const SAMPLE_EVENTS = { 'device': { 'mobile': 1 } + }, + 'getFloor': function() { + return { floor: 1.5, currency: 'USD' }; } } ], @@ -178,6 +180,9 @@ const SAMPLE_EVENTS = { 'device': { 'mobile': 1 } + }, + 'getFloor': function() { + return { floor: 1.5, currency: 'USD' }; } } ], @@ -275,6 +280,7 @@ const SAMPLE_EVENTS = { 'pbDg': '2.25', 'pbCg': '', 'size': '480x320', + 'dspId': 'test-dsp-id-123', 'adserverTargeting': { 'hb_bidder': 'zeta_global_ssp', 'hb_adid': '5759bb3ef7be1e8', @@ -337,6 +343,7 @@ const SAMPLE_EVENTS = { 'adUnitCode': '/19968336/header-bid-tag-0', 'timeToRespond': 123, 'size': '480x320', + 'dspId': 'test-dsp-id-123', 'adserverTargeting': { 'hb_bidder': 'zeta_global_ssp', 'hb_adid': '5759bb3ef7be1e8', @@ -351,7 +358,12 @@ const SAMPLE_EVENTS = { { 'nonZetaParam': 'nonZetaValue' } - ] + ], + 'floorData': { + 'floorValue': 1.5, + 'floorCurrency': 'USD', + 'floorRule': 'test-rule' + } }, 'adId': '5759bb3ef7be1e8' }, @@ -435,7 +447,10 @@ const SAMPLE_EVENTS = { } } }, - 'timeout': 3 + 'timeout': 3, + 'getFloor': function() { + return { floor: 0.75, currency: 'USD' }; + } }, { 'bidder': 'zeta_global_ssp', @@ -516,7 +531,10 @@ const SAMPLE_EVENTS = { } } }, - 'timeout': 3 + 'timeout': 3, + 'getFloor': function() { + return { floor: 0.75, currency: 'USD' }; + } } ] } @@ -562,14 +580,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { zetaAnalyticsAdapter.disableAnalytics(); }); - it('Handle events', function () { - this.timeout(3000); - + it('should handle AUCTION_END event', function () { events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); - events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); - events.emit(EVENTS.BID_TIMEOUT, SAMPLE_EVENTS.BID_TIMEOUT); - expect(requests.length).to.equal(3); + expect(requests.length).to.equal(1); const auctionEnd = JSON.parse(requests[0].requestBody); expect(auctionEnd).to.be.deep.equal({ zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, @@ -582,11 +596,15 @@ describe('Zeta Global SSP Analytics Adapter', function () { bidId: '206be9a13236af', auctionId: '75e394d9', bidder: 'zeta_global_ssp', - mediaType: 'BANNER', - size: '300x250', + mediaType: 'banner', + sizes: [ + [300, 250], + [300, 600] + ], device: { mobile: 1 - } + }, + floor: 1.5 }] }, { bidderCode: 'appnexus', @@ -597,11 +615,15 @@ describe('Zeta Global SSP Analytics Adapter', function () { bidId: '41badc0e164c758', auctionId: '75e394d9', bidder: 'appnexus', - mediaType: 'BANNER', - size: '300x250', + mediaType: 'banner', + sizes: [ + [300, 250], + [300, 600] + ], device: { mobile: 1 - } + }, + floor: 1.5 }] }], bidsReceived: [{ @@ -614,10 +636,17 @@ describe('Zeta Global SSP Analytics Adapter', function () { size: '480x320', adomain: 'example.adomain', timeToRespond: 123, - cpm: 2.258302852806723 + cpm: 2.258302852806723, + dspId: 'test-dsp-id-123' }] }); - const auctionSucceeded = JSON.parse(requests[1].requestBody); + }); + + it('should handle AD_RENDER_SUCCEEDED event', function () { + events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); + + expect(requests.length).to.equal(1); + const auctionSucceeded = JSON.parse(requests[0].requestBody); expect(auctionSucceeded.zetaParams).to.be.deep.equal({ sid: 111, tags: { @@ -634,15 +663,26 @@ describe('Zeta Global SSP Analytics Adapter', function () { auctionId: '75e394d9', creativeId: '456456456', bidder: 'zeta_global_ssp', + dspId: 'test-dsp-id-123', mediaType: 'banner', size: '480x320', adomain: 'example.adomain', timeToRespond: 123, - cpm: 2.258302852806723 + cpm: 2.258302852806723, + floorData: { + floorValue: 1.5, + floorCurrency: 'USD', + floorRule: 'test-rule' + } }); expect(auctionSucceeded.device.ua).to.not.be.empty; + }); + + it('should handle BID_TIMEOUT event', function () { + events.emit(EVENTS.BID_TIMEOUT, SAMPLE_EVENTS.BID_TIMEOUT); - const bidTimeout = JSON.parse(requests[2].requestBody); + expect(requests.length).to.equal(1); + const bidTimeout = JSON.parse(requests[0].requestBody); expect(bidTimeout.zetaParams).to.be.deep.equal({ sid: 111, tags: { @@ -656,8 +696,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { 'bidId': '27c8c05823e2f', 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', 'bidder': 'zeta_global_ssp', - 'mediaType': 'BANNER', - 'size': '300x250', + 'mediaType': 'banner', + 'sizes': [ + [300, 250] + ], 'timeout': 3, 'device': { 'w': 807, @@ -675,13 +717,16 @@ describe('Zeta Global SSP Analytics Adapter', function () { 'mobile': 0 } }, - 'adUnitCode': 'ad-1' + 'adUnitCode': 'ad-1', + 'floor': 0.75 }, { 'bidId': '31a3b551cbf1ed', 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', 'bidder': 'zeta_global_ssp', - 'mediaType': 'BANNER', - 'size': '300x250', + 'mediaType': 'banner', + 'sizes': [ + [300, 250] + ], 'timeout': 3, 'device': { 'w': 807, @@ -699,7 +744,8 @@ describe('Zeta Global SSP Analytics Adapter', function () { 'mobile': 0 } }, - 'adUnitCode': 'ad-2' + 'adUnitCode': 'ad-2', + 'floor': 0.75 }]); }); }); diff --git a/test/spec/ortb2.5Translator/translator_spec.js b/test/spec/ortb2.5Translator/translator_spec.js index db20a8f59be..a3d62417626 100644 --- a/test/spec/ortb2.5Translator/translator_spec.js +++ b/test/spec/ortb2.5Translator/translator_spec.js @@ -1,4 +1,10 @@ -import {EXT_PROMOTIONS, moveRule, splitPath, toOrtb25} from '../../../libraries/ortb2.5Translator/translator.js'; +import { + addExt, + EXT_PROMOTIONS, + moveRule, + splitPath, + toOrtb25, toOrtb26 +} from '../../../libraries/ortb2.5Translator/translator.js'; import {deepAccess, deepClone, deepSetValue} from '../../../src/utils.js'; describe('ORTB 2.5 translation', () => { @@ -33,10 +39,7 @@ describe('ORTB 2.5 translation', () => { }); describe('toOrtb25', () => { EXT_PROMOTIONS.forEach(path => { - const newPath = (() => { - const [prefix, field] = splitPath(path); - return `${prefix}.ext.${field}`; - })(); + const newPath = addExt(...splitPath(path)); it(`moves ${path} to ${newPath}`, () => { const obj = {}; @@ -61,4 +64,17 @@ describe('ORTB 2.5 translation', () => { }); }); }); + describe('toOrtb26', () => { + EXT_PROMOTIONS.forEach(path => { + const oldPath = addExt(...splitPath(path)); + + it(`moves ${oldPath} to ${path}`, () => { + const obj = {}; + deepSetValue(obj, oldPath, 'val'); + toOrtb26(obj); + expect(deepAccess(obj, oldPath)).to.eql(undefined); + expect(deepAccess(obj, path)).to.eql('val'); + }); + }); + }) }); diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index 7978a115eaa..b7be604b85b 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -39,22 +39,21 @@ describe('adRendering', () => { beforeEach(() => { sandbox.stub(auctionManager, 'findBidByAdId').callsFake(() => 'auction-bid') }); - it('should default to bid from auctionManager', () => { - return getBidToRender('adId', true, Promise.resolve(null)).then((res) => { - expect(res).to.eql('auction-bid'); - sinon.assert.calledWith(auctionManager.findBidByAdId, 'adId'); - }); - }); - it('should give precedence to override promise', () => { - return getBidToRender('adId', true, Promise.resolve('override')).then((res) => { - expect(res).to.eql('override'); - sinon.assert.notCalled(auctionManager.findBidByAdId); + it('should default to bid from auctionManager', async () => { + await new Promise((resolve) => { + getBidToRender('adId', true, (res) => { + expect(res).to.eql('auction-bid'); + sinon.assert.calledWith(auctionManager.findBidByAdId, 'adId'); + resolve(); + }) }) }); - it('should return undef when override rejects', () => { - return getBidToRender('adId', true, Promise.reject(new Error('any reason'))).then(res => { - expect(res).to.not.exist; - }) + it('should, by default, not give up the thread', () => { + let ran = false; + getBidToRender('adId', true, () => { + ran = true; + }); + expect(ran).to.be.true; }) }) describe('getRenderingData', () => { diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 686464b8b5c..b0a9adcd3a1 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,4 +1,5 @@ import { + canSetCookie, deviceAccessRule, getCoreStorageManager, newStorageManager, @@ -20,6 +21,7 @@ import { ACTIVITY_PARAM_STORAGE_TYPE } from '../../../../src/activities/params.js'; import {activityParams} from '../../../../src/activities/activityParams.js'; +import {registerActivityControl} from '../../../../src/activities/rules.js'; describe('storage manager', function() { before(() => { @@ -319,3 +321,38 @@ describe('storage manager', function() { }); }); }); + +describe('canSetCookie', () => { + let allow, unregisterACRule; + beforeEach(() => { + allow = true; + unregisterACRule = registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'test', (params) => { + if (params.component === 'prebid.storage') { + return {allow}; + } + }) + }); + afterEach(() => { + unregisterACRule(); + canSetCookie.clear(); + }) + + it('should return true when allowed', () => { + expect(canSetCookie()).to.be.true; + }); + it('should not leave stray cookies', () => { + const previousCookies = document.cookie; + canSetCookie(); + expect(previousCookies).to.eql(document.cookie); + }); + it('should return false when not allowed', () => { + allow = false; + expect(canSetCookie()).to.be.false; + }); + + it('should cache results', () => { + expect(canSetCookie()).to.be.true; + allow = false; + expect(canSetCookie()).to.be.true; + }) +}) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 3a439f7eb04..5939298765e 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -27,6 +27,7 @@ import {deepAccess, deepSetValue, generateUUID} from '../../../src/utils.js'; import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; import {BID_STATUS, EVENTS, GRANULARITY_OPTIONS, PB_LOCATOR, TARGETING_KEYS} from 'src/constants.js'; import {getBidToRender} from '../../../src/adRendering.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -201,15 +202,10 @@ window.apntag = { describe('Unit: Prebid Module', function () { let bidExpiryStub, sandbox; - function getBidToRenderHook(next, adId) { - // make sure we can handle async bidToRender - next(adId, new Promise((resolve) => setTimeout(resolve))) - } before((done) => { hook.ready(); pbjsModule.requestBids.getHooks().remove(); resetDebugging(); - getBidToRender.before(getBidToRenderHook, 100); // preload creative renderer getCreativeRenderer({}).then(() => done()); }); @@ -231,7 +227,6 @@ describe('Unit: Prebid Module', function () { after(function() { auctionManager.clearAllAuctions(); - getBidToRender.getHooks({hook: getBidToRenderHook}).remove(); }); describe('processQueue', () => { @@ -1238,6 +1233,7 @@ describe('Unit: Prebid Module', function () { } beforeEach(function () { + bidId++; doc = { write: sinon.spy(), close: sinon.spy(), @@ -1296,17 +1292,43 @@ describe('Unit: Prebid Module', function () { }) }); - it('should write the ad to the doc', function () { - const ad = ""; - pushBidResponseToAuction({ - ad + describe('when legacyRender is set', () => { + beforeEach(() => { + pbjs.setConfig({ + auctionOptions: { + legacyRender: true + } + }) }); - const iframe = {}; - doc.createElement.returns(iframe); - return renderAd(doc, bidId).then(() => { - expect(iframe.srcdoc).to.eql(ad); - }) - }); + + afterEach(() => { + configObj.resetConfig() + }); + + it('should immediately write the ad to the doc', function () { + pushBidResponseToAuction({ + ad: "" + }); + pbjs.renderAd(doc, bidId); + sinon.assert.calledWith(doc.write, adResponse.ad); + sinon.assert.called(doc.close); + }); + }) + + describe('when legacyRender is NOT set', () => { + it('should use an iframe, not document.write', function () { + const ad = ""; + pushBidResponseToAuction({ + ad + }); + const iframe = {}; + doc.createElement.returns(iframe); + return renderAd(doc, bidId).then(() => { + expect(iframe.srcdoc).to.eql(ad); + sinon.assert.notCalled(doc.write); + }) + }); + }) it('should place the url inside an iframe on the doc', function () { pushBidResponseToAuction({ @@ -1381,7 +1403,8 @@ describe('Unit: Prebid Module', function () { ad: "" }); return renderAd(doc, bidId).then(() => { - assert.deepEqual(pbjs.getAllWinningBids()[0], adResponse); + const winningBid = pbjs.getAllWinningBids().find(el => el.adId === adResponse.adId); + expect(winningBid).to.eql(adResponse); }); }); @@ -1640,6 +1663,117 @@ describe('Unit: Prebid Module', function () { sinon.assert.called(spec.onTimeout); }); + describe('requestBids event', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + + it('should be emitted with request', async () => { + const request = { + adUnits + } + await runAuction(request); + sinon.assert.calledWith(events.emit, EVENTS.REQUEST_BIDS, request); + }); + + it('should provide a request object when not supplied to requestBids()', async () => { + getGlobal().addAdUnits(adUnits); + try { + await runAuction(); + sinon.assert.calledWith(events.emit, EVENTS.REQUEST_BIDS, sinon.match({ + adUnits + })); + } finally { + adUnits.map(au => au.code).forEach(getGlobal().removeAdUnit) + } + }); + + it('should not leak internal state', async () => { + const request = { + adUnits + }; + await runAuction(Object.assign({}, request)); + expect(events.emit.args[0][1].metrics).to.not.exist; + }); + + describe('ad unit filter', () => { + let au, request; + + function requestBidsHook(next, req) { + request = req; + next(req); + } + before(() => { + pbjsModule.requestBids.before(requestBidsHook, 999); + }) + after(() => { + pbjsModule.requestBids.getHooks({hook: requestBidsHook}).remove(); + }) + + beforeEach(() => { + request = null; + au = { + ...adUnits[0], + code: 'au' + } + adUnits.push(au); + }); + it('should filter adUnits by code', async () => { + await runAuction({ + adUnits, + adUnitCodes: ['au'] + }); + sinon.assert.calledWith(events.emit, EVENTS.REQUEST_BIDS, sinon.match({ + adUnits: [au], + })); + }); + it('should still pass unfiltered ad units to requestBids', () => { + runAuction({ + adUnits: adUnits.slice(), + adUnitCodes: ['au'] + }); + expect(request.adUnits).to.have.deep.members(adUnits); + }); + + it('should allow event handlers to add ad units', () => { + const extraAu = { + ...adUnits[0], + code: 'extra' + } + events.emit.callsFake((evt, request) => { + request.adUnits.push(extraAu) + }); + runAuction({ + adUnits: adUnits.slice(), + adUnitCodes: ['au'] + }); + expect(request.adUnits).to.have.deep.members([...adUnits, extraAu]); + }); + + it('should allow event handlers to remove ad units', () => { + events.emit.callsFake((evt, request) => { + request.adUnits = []; + }); + runAuction({ + adUnits: adUnits.slice(), + adUnitCodes: ['au'] + }); + expect(request.adUnits).to.eql([adUnits[0]]); + }); + + it('should NOT allow event handlers to modify adUnitCodes', () => { + events.emit.callsFake((evt, request) => { + request.adUnitCodes = ['other'] + }); + runAuction({ + adUnits, + adUnitCodes: ['au'] + }); + expect(request.adUnitCodes).to.eql(['au']); + }) + }); + }) + it('should execute `onSetTargeting` after setTargetingForGPTAsync', async function () { const bidId = 1; const auctionId = 1; diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 24c7542b31d..084341358b4 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,4 +1,4 @@ -import {getReplier, receiveMessage, resizeRemoteCreative} from 'src/secureCreatives.js'; +import {getReplier, receiveMessage, resizeAnchor, resizeRemoteCreative} from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; import {auctionManager} from 'src/auctionManager.js'; @@ -21,9 +21,9 @@ import {getGlobal} from '../../../src/prebidGlobal.js'; describe('secureCreatives', () => { let sandbox; - function getBidToRenderHook(next, adId) { + function getBidToRenderHook(next, ...args) { // make sure that bids can be retrieved asynchronously - next(adId, new Promise((resolve) => setTimeout(resolve))) + setTimeout(() => next(...args)) } before(() => { getBidToRender.before(getBidToRenderHook); @@ -45,7 +45,9 @@ describe('secureCreatives', () => { } function receive(ev) { - return Promise.resolve(receiveMessage(ev)); + return new Promise((resolve) => { + receiveMessage(ev, resolve); + }) } describe('getReplier', () => { @@ -631,4 +633,65 @@ describe('secureCreatives', () => { sinon.assert.notCalled(document.getElementById); }) }) + + describe('resizeAnchor', () => { + let ins, clock; + beforeEach(() => { + clock = sinon.useFakeTimers(); + ins = { + style: { + width: 'auto', + height: 'auto' + } + } + }); + afterEach(() => { + clock.restore(); + }) + function setSize(width = '300px', height = '250px') { + ins.style.width = width; + ins.style.height = height; + } + it('should not change dimensions until they have been set externally', () => { + const pm = resizeAnchor(ins, 100, 200); + clock.tick(200); + expect(ins.style).to.eql({width: 'auto', height: 'auto'}); + setSize(); + clock.tick(200); + return pm.then(() => { + expect(ins.style.width).to.eql('100px'); + expect(ins.style.height).to.eql('200px'); + }) + }) + it('should quit trying if dimensions are never set externally', () => { + const pm = resizeAnchor(ins, 100, 200); + clock.tick(5000); + return pm + .then(() => { sinon.assert.fail('should have thrown') }) + .catch(err => { + expect(err.message).to.eql('Could not resize anchor') + }) + }); + it('should not choke when initial width/ height are null', () => { + ins.style = {}; + const pm = resizeAnchor(ins, 100, 200); + clock.tick(200); + setSize(); + clock.tick(200); + return pm.then(() => { + expect(ins.style.width).to.eql('100px'); + expect(ins.style.height).to.eql('200px'); + }) + }); + + it('should not resize dimensions that are set to 100%', () => { + const pm = resizeAnchor(ins, 100, 200); + setSize('100%', '250px'); + clock.tick(200); + return pm.then(() => { + expect(ins.style.width).to.eql('100%'); + expect(ins.style.height).to.eql('200px'); + }); + }) + }) }); diff --git a/test/spec/utils/yield_spec.js b/test/spec/utils/yield_spec.js new file mode 100644 index 00000000000..d2f9d188d3c --- /dev/null +++ b/test/spec/utils/yield_spec.js @@ -0,0 +1,85 @@ +import {pbYield, serialize} from '../../../src/utils/yield.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; + +describe('main thread yielding', () => { + let shouldYield, ran; + beforeEach(() => { + ran = false; + shouldYield = null; + }); + + function runYield() { + return new Promise((resolve) => { + pbYield(() => shouldYield, () => { + ran = true; + resolve(); + }); + }); + } + + describe('pbYield', () => { + [true, false].forEach(expectYield => { + it(`should ${!expectYield ? 'not ' : ''}yield when shouldYield returns ${expectYield}`, () => { + shouldYield = expectYield; + const pm = runYield(); + expect(ran).to.eql(!expectYield); + return pm; + }); + }); + + describe('when shouldYield = true', () => { + let scheduler; + beforeEach(() => { + shouldYield = true; + scheduler = { + yield: sinon.stub().callsFake(() => Promise.resolve()) + }; + }); + it('should use window.scheduler, when available', async () => { + window.scheduler = scheduler; + try { + await runYield(); + sinon.assert.called(scheduler.yield); + } finally { + delete window.scheduler; + } + }); + }); + }); + describe('serialize', () => { + it('runs each function in succession, when delayed', async () => { + let cbs = []; + const fn = (cb) => { + cbs.push(cb); + } + let done = false; + serialize([fn, fn], () => { + done = true; + }); + expect(cbs.length).to.equal(1); + expect(done).to.be.false; + await Promise.resolve(); + cbs[0](); + expect(cbs.length).to.equal(2); + expect(done).to.be.false; + cbs[1](); + expect(cbs.length).to.equal(2); + expect(done).to.be.true; + }); + it('runs each function in succession, when immediate', () => { + let results = []; + let i = 0; + const fn = (cb) => { + i++; + results.push(i); + cb(); + }; + let done = false; + serialize([fn, fn], () => { + done = true; + }); + expect(results).to.eql([1, 2]); + expect(done).to.be.true; + }); + }); +}); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 1efdc5621f6..8ed2efa3b9f 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1446,7 +1446,7 @@ describe('getWinDimensions', () => { }); it('should clear cache once per 20ms', () => { - const resetWinDimensionsSpy = sinon.spy(winDimensions.internal, 'reset'); + const resetWinDimensionsSpy = sinon.spy(winDimensions.internal.winDimensions, 'reset'); expect(getWinDimensions().innerHeight).to.exist; clock.tick(1); expect(getWinDimensions().innerHeight).to.exist; diff --git a/test/test_deps.js b/test/test_deps.js index 7047e775d9c..5f0ab890035 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -41,6 +41,14 @@ sinon.createFakeServerWithClock = fakeServerWithClock.create.bind(fakeServerWith localStorage.clear(); +if (window.frameElement != null) { + // sometimes (e.g. chrome headless) the tests run in an iframe that is offset from the top window + // other times (e.g. browser debug page) they run in the top window + // this can cause inconsistencies with the percentInView libraries; if we are in a frame, + // fake the same dimensions as the top window + window.frameElement.getBoundingClientRect = () => window.top.getBoundingClientRect(); +} + require('test/helpers/global_hooks.js'); require('test/helpers/consentData.js'); require('test/helpers/prebidGlobal.js');