diff --git a/.circleci/config.yml b/.circleci/config.yml
index 22539912268..dcf2ba804c6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,7 +7,7 @@ aliases:
- &environment
docker:
# specify the version you desire here
- - image: cimg/node:16.20-browsers
+ - image: cimg/node:20.14.0-browsers
resource_class: xlarge
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@@ -18,8 +18,6 @@ aliases:
- &restore_dep_cache
keys:
- v1-dependencies-{{ checksum "package.json" }}
- # fallback to using the latest cache if no exact match is found
- - v1-dependencies-
- &save_dep_cache
paths:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 69e13850258..9b1bb6e39cf 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,4 +1,4 @@
-ARG VARIANT="12"
+ARG VARIANT="20"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT}
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index f17c7a0063d..00000000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,99 +0,0 @@
-
-const allowedModules = require('./allowedModules.js');
-
-module.exports = {
- env: {
- browser: true,
- commonjs: true
- },
- settings: {
- 'import/resolver': {
- node: {
- moduleDirectory: ['node_modules', './']
- }
- },
- 'jsdoc': {
- mode: 'typescript',
- tagNamePreference: {
- 'tag constructor': 'constructor',
- extends: 'extends',
- method: 'method',
- return: 'return',
- }
- }
- },
- extends: [
- 'standard',
- 'plugin:jsdoc/recommended'
- ],
- plugins: [
- 'prebid',
- 'import',
- 'jsdoc'
- ],
- globals: {
- 'BROWSERSTACK_USERNAME': false,
- 'BROWSERSTACK_KEY': false,
- 'FEATURES': 'readonly',
- },
- // use babel as parser for fancy syntax
- parser: '@babel/eslint-parser',
- parserOptions: {
- sourceType: 'module',
- ecmaVersion: 2018,
- },
- ignorePatterns: ['libraries/creative-renderer*'],
-
- rules: {
- 'comma-dangle': 'off',
- semi: 'off',
- 'space-before-function-paren': 'off',
- 'import/extensions': ['error', 'ignorePackages'],
-
- // Exceptions below this line are temporary, so that eslint can be added into the CI process.
- // Violations of these styles should be fixed, and the exceptions removed over time.
- //
- // See Issue #1111.
- eqeqeq: 'off',
- 'no-return-assign': 'off',
- 'no-throw-literal': 'off',
- 'no-undef': 2,
- 'no-useless-escape': 'off',
- 'no-console': 'error',
- 'jsdoc/check-types': 'off',
- 'jsdoc/newline-after-description': 'off',
- 'jsdoc/require-jsdoc': 'off',
- 'jsdoc/require-param': 'off',
- 'jsdoc/require-param-description': 'off',
- 'jsdoc/require-param-name': 'off',
- 'jsdoc/require-param-type': 'off',
- 'jsdoc/require-property': 'off',
- 'jsdoc/require-property-description': 'off',
- 'jsdoc/require-property-name': 'off',
- 'jsdoc/require-property-type': 'off',
- 'jsdoc/require-returns': 'off',
- 'jsdoc/require-returns-check': 'off',
- 'jsdoc/require-returns-description': 'off',
- 'jsdoc/require-returns-type': 'off',
- 'jsdoc/require-yields': 'off',
- 'jsdoc/require-yields-check': 'off',
- 'jsdoc/tag-lines': 'off'
- },
- overrides: Object.keys(allowedModules).map((key) => ({
- files: key + '/**/*.js',
- rules: {
- 'prebid/validate-imports': ['error', allowedModules[key]],
- 'no-restricted-globals': [
- 'error',
- {
- name: 'require',
- message: 'use import instead'
- }
- ]
- }
- })).concat([{
- // code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain.
- files: 'plugins/*/**/*.js',
- parser: 'esprima'
- }])
-};
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 09ef5c445f2..367ace94d37 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -16,7 +16,8 @@ For any user facing change, submit a link to a PR on the docs repo at https://gi
- [ ] Bugfix
- [ ] Feature
-- [ ] New bidder adapter
+- [ ] New bidder adapter
+- [ ] Updated bidder adapter
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
@@ -40,7 +41,7 @@ For any user facing change, submit a link to a PR on the docs repo at https://gi
}
```
-Be sure to test the integration with your adserver using the [Hello World](/integrationExamples/gpt/hello_world.html) sample page. -->
+Be sure to test the integration with your adserver using the [Hello World](https://github.com/prebid/Prebid.js/blob/master/integrationExamples/gpt/hello_world.html) sample page. -->
## Other information
diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml
index 2e8465003e4..8d3788e8956 100644
--- a/.github/codeql/codeql-config.yml
+++ b/.github/codeql/codeql-config.yml
@@ -2,3 +2,6 @@ paths:
- src
- modules
- libraries
+queries:
+ - name: Prebid queries
+ uses: ./.github/codeql/queries
diff --git a/.github/codeql/queries/deviceMemory.ql b/.github/codeql/queries/deviceMemory.ql
new file mode 100644
index 00000000000..6f650abf0e1
--- /dev/null
+++ b/.github/codeql/queries/deviceMemory.ql
@@ -0,0 +1,14 @@
+/**
+ * @id prebid/device-memory
+ * @name Access to navigator.deviceMemory
+ * @kind problem
+ * @problem.severity warning
+ * @description Finds uses of deviceMemory
+ */
+
+import prebid
+
+from SourceNode nav
+where
+ nav = windowPropertyRead("navigator")
+select nav.getAPropertyRead("deviceMemory"), "deviceMemory is an indicator of fingerprinting"
diff --git a/.github/codeql/queries/hardwareConcurrency.ql b/.github/codeql/queries/hardwareConcurrency.ql
new file mode 100644
index 00000000000..350dbd1ae81
--- /dev/null
+++ b/.github/codeql/queries/hardwareConcurrency.ql
@@ -0,0 +1,14 @@
+/**
+ * @id prebid/hardware-concurrency
+ * @name Access to navigator.hardwareConcurrency
+ * @kind problem
+ * @problem.severity warning
+ * @description Finds uses of hardwareConcurrency
+ */
+
+import prebid
+
+from SourceNode nav
+where
+ nav = windowPropertyRead("navigator")
+select nav.getAPropertyRead("hardwareConcurrency"), "hardwareConcurrency is an indicator of fingerprinting"
diff --git a/.github/codeql/queries/prebid.qll b/.github/codeql/queries/prebid.qll
new file mode 100644
index 00000000000..02fb5adc93c
--- /dev/null
+++ b/.github/codeql/queries/prebid.qll
@@ -0,0 +1,36 @@
+import javascript
+import DataFlow
+
+SourceNode otherWindow() {
+ result = globalVarRef("top") or
+ result = globalVarRef("self") or
+ result = globalVarRef("parent") or
+ result = globalVarRef("frames").getAPropertyRead() or
+ result = DOM::documentRef().getAPropertyRead("defaultView")
+}
+
+SourceNode connectedWindow(SourceNode win) {
+ result = win.getAPropertyRead("self") or
+ result = win.getAPropertyRead("top") or
+ result = win.getAPropertyRead("parent") or
+ result = win.getAPropertyRead("frames").getAPropertyRead() or
+ result = win.getAPropertyRead("document").getAPropertyRead("defaultView")
+}
+
+SourceNode relatedWindow(SourceNode win) {
+ result = connectedWindow(win) or
+ result = relatedWindow+(connectedWindow(win))
+}
+
+SourceNode anyWindow() {
+ result = otherWindow() or
+ result = relatedWindow(otherWindow())
+}
+
+/*
+ Matches uses of property `prop` done on any window object.
+*/
+SourceNode windowPropertyRead(string prop) {
+ result = globalVarRef(prop) or
+ result = anyWindow().getAPropertyRead(prop)
+}
diff --git a/.github/codeql/queries/qlpack.yml b/.github/codeql/queries/qlpack.yml
new file mode 100644
index 00000000000..72e90d3de9b
--- /dev/null
+++ b/.github/codeql/queries/qlpack.yml
@@ -0,0 +1,8 @@
+---
+library: false
+warnOnImplicitThis: false
+name: queries
+version: 0.0.1
+dependencies:
+ codeql/javascript-all: ^1.1.1
+ codeql/javascript-queries: ^1.1.0
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index a3246cffd6d..5876dfa0138 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -1,6 +1,10 @@
name-template: 'Prebid $RESOLVED_VERSION Release'
tag-template: '$RESOLVED_VERSION'
+autolabeler:
+ - label: 'maintenance'
+ title:
+ - '/^(?!.*(bug|initial|release|fix)).*$/i'
categories:
- title: '🚀 New Features'
label: 'feature'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6bfde353bb7..4e3a8e19f29 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
- run: npm install && npm install -g gulp
- name: Run tests
- run: node_modules/.bin/gulp test --file test/spec/modules/ringieraxelspringerBidAdapter_spec.js,test/spec/modules/dasBidAdapter_spec.js
+ run: node_modules/.bin/gulp test --file test/spec/modules/ringieraxelspringerBidAdapter_spec.js --file test/spec/modules/dasBidAdapter_spec.js
- name: build
run: node_modules/.bin/gulp build --modules=modules.json
@@ -65,4 +65,4 @@ jobs:
release="${version}_$(date +"%Y%m%d%H%M%S")"
echo "Uploading Prebid.js ver:${version} to OCDN"
echo -e "\e[32mRelease: ${release}\e[0m"
- aws s3 sync "${version}" s3://${BUCKET}/prebid/prod/${release}/ --region ocdn --endpoint "https://ocdn.eu"
\ No newline at end of file
+ aws s3 sync "${version}" s3://${BUCKET}/prebid/prod/${release}/ --region ocdn --endpoint "https://ocdn.eu"
diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml
new file mode 100644
index 00000000000..d98ae8e2d72
--- /dev/null
+++ b/.github/workflows/code-path-changes.yml
@@ -0,0 +1,37 @@
+name: Notify Code Path Changes
+
+on:
+ pull_request_target:
+ types: [opened, synchronize]
+ paths:
+ - '**'
+
+env:
+ OAUTH2_CLIENT_ID: ${{ secrets.OAUTH2_CLIENT_ID }}
+ OAUTH2_CLIENT_SECRET: ${{ secrets.OAUTH2_CLIENT_SECRET }}
+ OAUTH2_REFRESH_TOKEN: ${{ secrets.OAUTH2_REFRESH_TOKEN }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+permissions:
+ contents: read
+
+jobs:
+ notify:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+
+ - name: Install dependencies
+ run: npm install axios nodemailer
+
+ - name: Run Notification Script
+ run: |
+ node .github/workflows/scripts/send-notification-on-change.js
diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml
new file mode 100644
index 00000000000..de5f1408dff
--- /dev/null
+++ b/.github/workflows/jscpd.yml
@@ -0,0 +1,124 @@
+name: Check for Duplicated Code
+
+on:
+ pull_request_target:
+ branches:
+ - master
+
+jobs:
+ check-duplication:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetch all history for all branches
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: |
+ npm install -g jscpd diff-so-fancy
+
+ - name: Create jscpd config file
+ run: |
+ echo '{
+ "threshold": 20,
+ "minTokens": 100,
+ "reporters": [
+ "json"
+ ],
+ "output": "./",
+ "pattern": "**/*.js",
+ "ignore": "**/*spec.js"
+ }' > .jscpd.json
+
+ - name: Run jscpd on entire codebase
+ run: jscpd
+
+ - name: Fetch base and target branches
+ run: |
+ git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}
+ git fetch origin +refs/pull/${{ github.event.pull_request.number }}/merge:refs/remotes/pull/${{ github.event.pull_request.number }}/merge
+
+ - name: Get the diff
+ run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge > changed_files.txt
+
+ - name: List generated files (debug)
+ run: ls -l
+
+ - name: Upload unfiltered jscpd report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: unfiltered-jscpd-report
+ path: ./jscpd-report.json
+
+ - name: Filter jscpd report for changed files
+ run: |
+ if [ ! -f ./jscpd-report.json ]; then
+ echo "jscpd-report.json not found"
+ exit 1
+ fi
+ echo "Filtering jscpd report for changed files..."
+ CHANGED_FILES=$(jq -R -s -c 'split("\n")[:-1]' changed_files.txt)
+ echo "Changed files: $CHANGED_FILES"
+ jq --argjson changed_files "$CHANGED_FILES" '
+ .duplicates | map(select(
+ (.firstFile?.name as $fname | $changed_files | any(. == $fname)) or
+ (.secondFile?.name as $sname | $changed_files | any(. == $sname))
+ ))
+ ' ./jscpd-report.json > filtered-jscpd-report.json
+ cat filtered-jscpd-report.json
+
+ - name: Check if filtered jscpd report exists
+ id: check_filtered_report
+ run: |
+ if [ $(wc -l < ./filtered-jscpd-report.json) -gt 1 ]; then
+ echo "filtered_report_exists=true" >> $GITHUB_ENV
+ else
+ echo "filtered_report_exists=false" >> $GITHUB_ENV
+ fi
+
+ - name: Upload filtered jscpd report
+ if: env.filtered_report_exists == 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: filtered-jscpd-report
+ path: ./filtered-jscpd-report.json
+
+ - name: Post GitHub comment
+ if: env.filtered_report_exists == 'true'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const filteredReport = JSON.parse(fs.readFileSync('filtered-jscpd-report.json', 'utf8'));
+ let comment = "Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:\n\n";
+ function link(dup) {
+ return `https://github.com/${{ github.event.repository.full_name }}/blob/${{ github.event.pull_request.head.sha }}/${dup.name}#L${dup.start + 1}-L${dup.end - 1}`
+ }
+ filteredReport.forEach(duplication => {
+ const firstFile = duplication.firstFile;
+ const secondFile = duplication.secondFile;
+ const lines = duplication.lines;
+ comment += `- [\`${firstFile.name}\`](${link(firstFile)}) has ${lines} duplicated lines with [\`${secondFile.name}\`](${link(secondFile)})\n`;
+ });
+ comment += "\nReducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀";
+ github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: comment
+ });
+
+ - name: Fail if duplications are found
+ if: env.filtered_report_exists == 'true'
+ run: |
+ echo "Duplications found, failing the check."
+ exit 1
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
new file mode 100644
index 00000000000..03ef6478f1c
--- /dev/null
+++ b/.github/workflows/linter.yml
@@ -0,0 +1,110 @@
+name: Check for linter warnings / exceptions
+
+on:
+ pull_request_target:
+ branches:
+ - master
+
+jobs:
+ check-linter:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.base.sha }}
+
+ - name: Fetch base and target branches
+ run: |
+ git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}
+ git fetch origin +refs/pull/${{ github.event.pull_request.number }}/merge:refs/remotes/pull/${{ github.event.pull_request.number }}/merge
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Get the diff
+ run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge | grep '^\(modules\|src\|libraries\|creative\)/.*\.js$' > __changed_files.txt || true
+
+ - name: Run linter on base branch
+ run: npx eslint --no-inline-config --format json $(cat __changed_files.txt | xargs stat --printf '%n\n' 2> /dev/null) > __base.json || true
+
+ - name: Check out PR
+ run: git checkout ${{ github.event.pull_request.head.sha }}
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run linter on PR
+ run: npx eslint --no-inline-config --format json $(cat __changed_files.txt | xargs stat --printf '%n\n' 2> /dev/null) > __pr.json || true
+
+ - name: Compare them and post comment if necessary
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const path = require('path');
+ const process = require('process');
+
+ function parse(fn) {
+ return JSON.parse(fs.readFileSync(fn)).reduce((memo, data) => {
+ const file = path.relative(process.cwd(), data.filePath);
+ if (!memo.hasOwnProperty(file)) { memo[file] = { errors: 0, warnings: 0} }
+ data.messages.forEach(({severity}) => {
+ memo[file][severity > 1 ? 'errors' : 'warnings']++;
+ });
+ return memo;
+ }, {})
+ }
+
+ function mkDiff(old, new_) {
+ const files = Object.fromEntries(
+ Object.entries(new_)
+ .map(([file, {errors, warnings}]) => {
+ const {errors: oldErrors, warnings: oldWarnings} = old[file] || {};
+ return [file, {errors: Math.max(0, errors - (oldErrors ?? 0)), warnings: Math.max(0, warnings - (oldWarnings ?? 0))}]
+ })
+ .filter(([_, {errors, warnings}]) => errors > 0 || warnings > 0)
+ )
+ return Object.values(files).reduce((memo, {warnings, errors}) => {
+ memo.errors += errors;
+ memo.warnings += warnings;
+ return memo;
+ }, {errors: 0, warnings: 0, files})
+ }
+
+ function mkComment({errors, warnings, files}) {
+ function pl(noun, number) {
+ return noun + (number === 1 ? '' : 's')
+ }
+ if (errors === 0 && warnings === 0) return;
+ const summary = [];
+ if (errors) summary.push(`**${errors}** linter ${pl('error', errors)}`)
+ if (warnings) summary.push(`**${warnings}** linter ${pl('warning', warnings)}`)
+ let cm = `Tread carefully! This PR adds ${summary.join(' and ')} (possibly disabled through directives):\n\n`;
+ Object.entries(files).forEach(([file, {errors, warnings}]) => {
+ const summary = [];
+ if (errors) summary.push(`+${errors} ${pl('error', errors)}`);
+ if (warnings) summary.push(`+${warnings} ${pl('warning', warnings)}`)
+ cm += ` * \`${file}\` (${summary.join(', ')})\n`
+ })
+ return cm;
+ }
+
+ const [base, pr] = ['__base.json', '__pr.json'].map(parse);
+ const comment = mkComment(mkDiff(base, pr));
+
+ if (comment) {
+ github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: comment
+ });
+ }
diff --git a/.github/workflows/scripts/codepath-notification b/.github/workflows/scripts/codepath-notification
new file mode 100644
index 00000000000..cdbc30b0e97
--- /dev/null
+++ b/.github/workflows/scripts/codepath-notification
@@ -0,0 +1,18 @@
+# when a changed file paths matches the regex, send an alert email
+# structure of the file is:
+#
+# javascriptRegex : email address
+#
+# For example, in the Prebid.js repo, the file pattern is generally
+#
+# /modules/BIDDERCODE
+# /spec/modules/BIDDERCODE
+#
+# The aim is to find a minimal set of regex patterns that matches any file in these paths
+
+rubicon|magnite : header-bidding@magnite.com
+/modules/ix|/spec/modules/ix : pdu-supply-prebid@indexexchange.com
+appnexus : prebid@microsoft.com
+pubmatic : header-bidding@pubmatic.com
+openx : prebid@openx.com
+medianet : prebid@media.net
diff --git a/.github/workflows/scripts/send-notification-on-change.js b/.github/workflows/scripts/send-notification-on-change.js
new file mode 100644
index 00000000000..57079ef37cb
--- /dev/null
+++ b/.github/workflows/scripts/send-notification-on-change.js
@@ -0,0 +1,139 @@
+// send-notification-on-change.js
+//
+// called by the code-path-changes.yml workflow, this script queries github for
+// the changes in the current PR, checks the config file for whether any of those
+// file paths are set to alert an email address, and sends email to multiple
+// parties if needed
+
+const fs = require('fs');
+const path = require('path');
+const axios = require('axios');
+const nodemailer = require('nodemailer');
+
+async function getAccessToken(clientId, clientSecret, refreshToken) {
+ try {
+ const response = await axios.post('https://oauth2.googleapis.com/token', {
+ client_id: clientId,
+ client_secret: clientSecret,
+ refresh_token: refreshToken,
+ grant_type: 'refresh_token',
+ });
+ return response.data.access_token;
+ } catch (error) {
+ console.error('Failed to fetch access token:', error.response?.data || error.message);
+ process.exit(1);
+ }
+}
+
+(async () => {
+ const configFilePath = path.join(__dirname, 'codepath-notification');
+ const repo = process.env.GITHUB_REPOSITORY;
+ const prNumber = process.env.GITHUB_PR_NUMBER;
+ const token = process.env.GITHUB_TOKEN;
+
+ // Generate OAuth2 access token
+ const clientId = process.env.OAUTH2_CLIENT_ID;
+ const clientSecret = process.env.OAUTH2_CLIENT_SECRET;
+ const refreshToken = process.env.OAUTH2_REFRESH_TOKEN;
+
+ // validate params
+ if (!repo || !prNumber || !token || !clientId || !clientSecret || !refreshToken) {
+ console.error('Missing required environment variables.');
+ process.exit(1);
+ }
+
+ // the whole process is in a big try/catch. e.g. if the config file doesn't exist, github is down, etc.
+ try {
+ // Read and process the configuration file
+ const configFileContent = fs.readFileSync(configFilePath, 'utf-8');
+ const configRules = configFileContent
+ .split('\n')
+ .filter(line => line.trim() !== '' && !line.trim().startsWith('#')) // Ignore empty lines and comments
+ .map(line => {
+ const [regex, email] = line.split(':').map(part => part.trim());
+ return { regex: new RegExp(regex), email };
+ });
+
+ // Fetch changed files from github
+ const [owner, repoName] = repo.split('/');
+ const apiUrl = `https://api.github.com/repos/${owner}/${repoName}/pulls/${prNumber}/files`;
+ const response = await axios.get(apiUrl, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/vnd.github.v3+json',
+ },
+ });
+
+ const changedFiles = response.data.map(file => file.filename);
+ console.log('Changed files:', changedFiles);
+
+ // match file pathnames that are in the config and group them by email address
+ const matchesByEmail = {};
+ changedFiles.forEach(file => {
+ configRules.forEach(rule => {
+ if (rule.regex.test(file)) {
+ if (!matchesByEmail[rule.email]) {
+ matchesByEmail[rule.email] = [];
+ }
+ matchesByEmail[rule.email].push(file);
+ }
+ });
+ });
+
+ // Exit successfully if no matches were found
+ if (Object.keys(matchesByEmail).length === 0) {
+ console.log('No matches found. Exiting successfully.');
+ process.exit(0);
+ }
+
+ console.log('Grouped matches by email:', matchesByEmail);
+
+ // get ready to email the changes
+ const accessToken = await getAccessToken(clientId, clientSecret, refreshToken);
+
+ // Configure Nodemailer with OAuth2
+ // service: 'Gmail',
+ const transporter = nodemailer.createTransport({
+ host: "smtp.gmail.com",
+ port: 465,
+ secure: true,
+ auth: {
+ type: 'OAuth2',
+ user: 'info@prebid.org',
+ clientId: clientId,
+ clientSecret: clientSecret,
+ refreshToken: refreshToken,
+ accessToken: accessToken
+ },
+ });
+
+ // Send one email per recipient
+ for (const [email, files] of Object.entries(matchesByEmail)) {
+ const emailBody = `
+ ${email},
+
+ Files relevant to your integration have been changed in open source ${repo}. The pull request is #${prNumber}. These are the files you monitor that have been modified:
+
+ ${files.map(file => `
${file}
`).join('')}
+
+ `;
+
+ try {
+ await transporter.sendMail({
+ from: `"Prebid Info" `,
+ to: email,
+ subject: `Files have been changed in open source ${repo}`,
+ html: emailBody,
+ });
+
+ console.log(`Email sent successfully to ${email}`);
+ console.log(`${emailBody}`);
+ } catch (error) {
+ console.error(`Failed to send email to ${email}:`, error.message);
+ }
+ }
+ } catch (error) {
+ console.error('Error:', error.message);
+ process.exit(1);
+ }
+})();
diff --git a/.gitignore b/.gitignore
index e5f000dd4d5..080b95e0753 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# Built Files
node_modules/
build
+# dist and npmignore are generated by "gulp build"
+/dist/
+.npmignore
# Test Files
test/app
diff --git a/.nvmrc b/.nvmrc
index 66df3b7ab2d..f203ab89b79 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-12.16.1
+20.13.1
diff --git a/PR_REVIEW.md b/PR_REVIEW.md
index 9deac9963fb..f6a2c157d2d 100644
--- a/PR_REVIEW.md
+++ b/PR_REVIEW.md
@@ -23,10 +23,11 @@ General gulp commands include separate commands for serving the codebase on a bu
- Checkout the branch (these instructions are available on the GitHub PR page as well).
- Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests.
- Verify code under review has at least 80% unit test coverage. If legacy code doesn't have enough unit test coverage, require that additional unit tests to be included in the PR.
-- Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test`
+- Verify tests are green in circle-ci + local build by running `gulp serve` | `gulp test`
- Verify no code quality violations are present from linting (should be reported in terminal)
- Make sure the code is not setting cookies or localstorage directly -- it must use the `StorageManager`.
- Review for obvious errors or bad coding practice / use best judgement here.
+- Don't allow needless code duplication with other js files; require both files import common code. Do not allow commits designed to fool the code duplication checker.
- If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change.
- If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed.
- If all above is good, add a `LGTM` comment and, if the change is in PBS-core or is an important module like the prebidServerBidAdapter, request 1 additional core member to review.
@@ -51,20 +52,21 @@ Follow steps above for general review process. In addition, please verify the fo
- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed.
- All bidder parameter conventions must be followed:
- Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit.
- - First party data must be read from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd).
+ - First party data must be read from the bid request object: bidrequest.ortb2
- Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function.
- Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain.
- The bidderRequest.refererInfo.referer must be checked in addition to any bidder-specific parameter.
- Page position must come from bidrequest.mediaTypes.banner.pos or bidrequest.mediaTypes.video.pos
- - Global OpenRTB fields should come from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd):
+ - Eids object is to be preferred to Userids object in the bid request, as the userid object may be removed in a future version
+ - Global OpenRTB fields should come from bidrequest.ortb2
- bcat, battr, badv
- Impression-specific OpenRTB fields should come from bidrequest.ortb2imp
- instl
- Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file):
- - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true`
- - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true`
+ - If they support the TCF consentManagementTcf module and TCF2, add `tcf2_supported: true`
- If they support the US Privacy consentManagementUsp module, add `usp_supported: true`
- - If they support one or more userId modules, add `userId: (list of supported vendors)`
+ - If they support the GPP consentManagementGpp module, add `gpp_supported: true`
+ - If they support one or more userId modules, add `userId: (list of supported vendors) or (all)`
- If they support video and/or native mediaTypes add `media_types: video, native`. Note that display is added by default. If you don't support display, add "no-display" as the first entry, e.g. `media_types: no-display, native`
- If they support COPPA, add `coppa_supported: true`
- If they support SChain, add `schain_supported: true`
@@ -100,7 +102,7 @@ Follow steps above for general review process. In addition:
- modules/userId/userId.md
- tests can go either within the userId_spec.js file or in their own _spec file if they wish
- GVLID is recommended in the *IdSystem file if they operate in EU
-- make sure example configurations align to the actual code (some modules use the userId storage settings and allow pub configuration, while others handle reading/writing cookies on their own, so should not include the storage params in examples)
+- make sure example configurations align to the actual code (some modules use the userId storage settings and allow pub configuration, while others handle reading/writing cookies on their own, so should not include the storage params in examples). This ability to write will be removed in a future version, see https://github.com/prebid/Prebid.js/issues/10710
- the 3 available methods (getId, extendId, decode) should be used as they were intended
- decode (required method) should not be making requests to retrieve a new ID, it should just be decoding a response
- extendId (optional method) should not be making requests to retrieve a new ID, it should just be adding additional data to the id object
@@ -121,6 +123,7 @@ Follow steps above for general review process. In addition:
- Confirm that the module
- is not loading external code. If it is, escalate to the #prebid-js Slack channel.
- is reading `config` from the function signature rather than calling `getConfig`.
+ - Is practicing reasonable data minimization, eg not sending all eids over the wire without publisher whitelisting
- is sending data to the bid request only as either First Party Data or in bidRequest.rtd.RTDPROVIDERCODE.
- is making HTTPS requests as early as possible, but not more often than needed.
- doesn't force bid adapters to load additional code.
diff --git a/README.md b/README.md
index 70e058d122b..2644419886c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
[](https://circleci.com/gh/prebid/Prebid.js)
-[](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open")
+[](https://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open")
[](https://coveralls.io/github/prebid/Prebid.js)
# Prebid.js
@@ -7,8 +7,8 @@
> A free and open source library for publishers to quickly implement header bidding.
This README is for developers who want to contribute to Prebid.js.
-Additional documentation can be found at [the Prebid homepage](http://prebid.org).
-Working examples can be found in [the developer docs](http://prebid.org/dev-docs/getting-started.html).
+Additional documentation can be found at [the Prebid.js documentation homepage](https://docs.prebid.org/prebid/prebidjs.html).
+Working examples can be found in [the developer docs](https://prebid.org/dev-docs/getting-started.html).
Prebid.js is open source software that is offered for free as a convenience. While it is designed to help companies address legal requirements associated with header bidding, we cannot and do not warrant that your use of Prebid.js will satisfy legal requirements. You are solely responsible for ensuring that your use of Prebid.js complies with all applicable laws. We strongly encourage you to obtain legal advice when using Prebid.js to ensure your implementation complies with all laws where you operate.
@@ -26,7 +26,7 @@ Prebid.js is open source software that is offered for free as a convenience. Whi
*Note:* Requires Prebid.js v1.38.0+
-Prebid.js depends on Babel and some Babel Plugins in order to run correctly in the browser. Here are some examples for
+Prebid.js depends on Babel and some Babel Plugins in order to run correctly in the browser. Here are some examples for
configuring webpack to work with Prebid.js.
With Babel 7:
@@ -37,7 +37,7 @@ module.exports = {
mode: 'production',
module: {
rules: [
-
+
// this rule can be excluded if you don't require babel-loader for your other application files
{
test: /\.m?js$/,
@@ -46,7 +46,7 @@ module.exports = {
loader: 'babel-loader',
}
},
-
+
// this separate rule is required to make sure that the Prebid.js files are babel-ified. this rule will
// override the regular exclusion from above (for being inside node_modules).
{
@@ -71,7 +71,7 @@ Or for Babel 6:
// you must manually install and specify the presets and plugins yourself
options: {
plugins: [
- "transform-object-assign", // required (for IE support) and "babel-plugin-transform-object-assign"
+ "transform-object-assign", // required (for IE support) and "babel-plugin-transform-object-assign"
// must be installed as part of your package.
require('prebid.js/plugins/pbjsGlobals.js') // required!
],
@@ -79,7 +79,7 @@ Or for Babel 6:
["env", { // you can use other presets if you wish.
"targets": { // this example is using "babel-presets-env", which must be installed if you
"browsers": [ // follow this example.
- ... // your browser targets. they should probably match the targets you're using for the rest
+ ... // your browser targets. they should probably match the targets you're using for the rest
// of your application
]
}
@@ -143,7 +143,7 @@ This will run testing but not linting. A web server will start at `http://localh
Development may be a bit slower but if you prefer linting and additional watch files you can also still run just:
- $ gulp serve
+ $ gulp serve
### Build Optimization
@@ -162,11 +162,11 @@ Building with just these adapters will result in a smaller bundle which should a
- Then run the build:
$ gulp build --modules=openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter
-
+
Alternatively, a `.json` file can be specified that contains a list of modules you would like to include.
$ gulp build --modules=modules.json
-
+
With `modules.json` containing the following
```json modules.json
[
@@ -202,7 +202,7 @@ gulp bundle --tag one --modules=one.json
gulp bundle --tag two --modules=two.json
```
-This generates slightly larger files, but has the advantage of being much faster to run (after the initial `gulp build`). It's also the method used by [the Prebid.org download page](https://docs.prebid.org/download.html).
+This generates slightly larger files, but has the advantage of being much faster to run (after the initial `gulp build`). It's also the method used by [the Prebid.org download page](https://docs.prebid.org/download.html).
@@ -227,7 +227,34 @@ Or, if you are consuming Prebid through npm, with the `disableFeatures` option i
}
```
-**Note**: this is still a work in progress - at the moment, `NATIVE` is the only feature that can be disabled this way, resulting in a minimal decrease in size (but you can expect that to improve over time).
+Features that can be disabled this way are:
+
+ - `VIDEO` - support for video bids;
+ - `NATIVE` - support for native bids;
+ - `UID2_CSTG` - support for UID2 client side token generation (see [Unified ID 2.0](https://docs.prebid.org/dev-docs/modules/userid-submodules/unified2.html))
+ - `GREEDY` - disables the use blocking, "greedy" promises within Prebid (see below).
+
+#### Greedy promises
+
+By default, Prebid attempts to hold control of the main thread when possible, using a [custom implementation of `Promise`](https://github.com/prebid/Prebid.js/blob/master/libraries/greedy/greedyPromise.js) that does not submit callbacks to the scheduler once the promise is resolved (running them immediately instead).
+Disabling this behavior instructs Prebid to use the standard `window.Promise` instead; this has the effect of breaking up task execution, making them slower overall but giving the browser more chances to run other tasks in between, which can improve UX.
+
+You may also override the `Promise` constructor used by Prebid through `pbjs.Promise`, for example:
+
+```javascript
+var pbjs = pbjs || {};
+pbjs.Promise = myCustomPromiseConstructor;
+```
+
+## Unminified code
+
+You can get a version of the code that's unminified for debugging with `build-bundle-dev`:
+
+```bash
+gulp build-bundle-dev --modules=bidderA,module1,...
+```
+
+The results will be in build/dev/prebid.js.
## Test locally
@@ -364,11 +391,11 @@ The results will be in
*Note*: Starting in June 2016, all pull requests to Prebid.js need to include tests with greater than 80% code coverage before they can be merged. For more information, see [#421](https://github.com/prebid/Prebid.js/issues/421).
-For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http://prebid.org/dev-docs/testing-prebid.html).
+For instructions on writing tests for Prebid.js, see [Testing Prebid.js](https://prebid.org/dev-docs/testing-prebid.html).
### Supported Browsers
-Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11.
+Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11.
### Governance
Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md).
diff --git a/allowedModules.js b/allowedModules.js
deleted file mode 100644
index 75ad4141a6c..00000000000
--- a/allowedModules.js
+++ /dev/null
@@ -1,18 +0,0 @@
-
-module.exports = {
- 'modules': [
- 'criteo-direct-rsa-validate',
- 'crypto-js',
- 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/
- ],
- 'src': [
- 'fun-hooks/no-eval',
- 'just-clone',
- 'dlv',
- 'dset'
- ],
- 'libraries': [
- ],
- 'creative': [
- ]
-};
diff --git a/browsers.json b/browsers.json
index bd6bd5772d6..0649a13e873 100644
--- a/browsers.json
+++ b/browsers.json
@@ -1,39 +1,39 @@
{
- "bs_edge_latest_windows_10": {
+ "bs_edge_latest_windows_11": {
"base": "BrowserStack",
- "os_version": "10",
+ "os_version": "11",
"browser": "edge",
"browser_version": "latest",
"device": null,
"os": "Windows"
},
- "bs_chrome_latest_windows_10": {
+ "bs_chrome_latest_windows_11": {
"base": "BrowserStack",
- "os_version": "10",
+ "os_version": "11",
"browser": "chrome",
"browser_version": "latest",
"device": null,
"os": "Windows"
},
- "bs_chrome_87_windows_10": {
+ "bs_chrome_107_windows_10": {
"base": "BrowserStack",
"os_version": "10",
"browser": "chrome",
- "browser_version": "87.0",
+ "browser_version": "107.0",
"device": null,
"os": "Windows"
},
- "bs_firefox_latest_windows_10": {
+ "bs_firefox_latest_windows_11": {
"base": "BrowserStack",
- "os_version": "10",
+ "os_version": "11",
"browser": "firefox",
"browser_version": "latest",
"device": null,
"os": "Windows"
},
- "bs_safari_latest_mac_bigsur": {
+ "bs_safari_latest_mac": {
"base": "BrowserStack",
- "os_version": "Big Sur",
+ "os_version": "Sonoma",
"browser": "safari",
"browser_version": "latest",
"device": null,
@@ -41,11 +41,11 @@
},
"bs_safari_15_catalina": {
"base": "BrowserStack",
- "os_version": "Catalina",
+ "os_version": "Monterey",
"browser": "safari",
- "browser_version": "13.1",
+ "browser_version": "15.6",
"device": null,
"os": "OS X"
}
-
+
}
diff --git a/creative/constants.js b/creative/constants.js
index d02c4c9d5e4..5748324f02c 100644
--- a/creative/constants.js
+++ b/creative/constants.js
@@ -1,6 +1,9 @@
+
// eslint-disable-next-line prebid/validate-imports
-import { AD_RENDER_FAILED_REASON, EVENTS, MESSAGES } from '../src/constants.js';
+import {AD_RENDER_FAILED_REASON, EVENTS, MESSAGES} from '../src/constants.js';
+// eslint-disable-next-line prebid/validate-imports
+export {PB_LOCATOR} from '../src/constants.js';
export const MESSAGE_REQUEST = MESSAGES.REQUEST;
export const MESSAGE_RESPONSE = MESSAGES.RESPONSE;
export const MESSAGE_EVENT = MESSAGES.EVENT;
diff --git a/creative/crossDomain.js b/creative/crossDomain.js
index a851885bfc0..d3524f61d4b 100644
--- a/creative/crossDomain.js
+++ b/creative/crossDomain.js
@@ -1,9 +1,11 @@
import {
ERROR_EXCEPTION,
- EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED,
+ EVENT_AD_RENDER_FAILED,
+ EVENT_AD_RENDER_SUCCEEDED,
MESSAGE_EVENT,
MESSAGE_REQUEST,
- MESSAGE_RESPONSE
+ MESSAGE_RESPONSE,
+ PB_LOCATOR
} from './constants.js';
const mkFrame = (() => {
@@ -24,14 +26,27 @@ const mkFrame = (() => {
};
})();
+function isPrebidWindow(win) {
+ return !!win.frames[PB_LOCATOR];
+}
+
export function renderer(win) {
+ let target = win.parent;
+ try {
+ while (target !== win.top && !isPrebidWindow(target)) {
+ target = target.parent;
+ }
+ if (!isPrebidWindow(target)) target = win.parent;
+ } catch (e) {
+ }
+
return function ({adId, pubUrl, clickUrl}) {
const pubDomain = new URL(pubUrl, window.location).origin;
function sendMessage(type, payload, responseListener) {
const channel = new MessageChannel();
channel.port1.onmessage = guard(responseListener);
- win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]);
+ target.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]);
}
function onError(e) {
@@ -77,7 +92,7 @@ export function renderer(win) {
W.Promise.resolve(W.render(data, {sendMessage, mkFrame}, win)).then(
() => sendMessage(MESSAGE_EVENT, {event: EVENT_AD_RENDER_SUCCEEDED}),
onError
- )
+ );
});
win.document.body.appendChild(renderer);
}
diff --git a/creative/renderers/display/renderer.js b/creative/renderers/display/renderer.js
index e031679b116..5a493bf1249 100644
--- a/creative/renderers/display/renderer.js
+++ b/creative/renderers/display/renderer.js
@@ -1,20 +1,30 @@
import {ERROR_NO_AD} from './constants.js';
-export function render({ad, adUrl, width, height}, {mkFrame}, win) {
+export function render({ad, adUrl, width, height, instl}, {mkFrame}, win) {
if (!ad && !adUrl) {
throw {
reason: ERROR_NO_AD,
message: 'Missing ad markup or URL'
};
} else {
+ if (height == null) {
+ const body = win.document?.body;
+ [body, body?.parentElement].filter(elm => elm?.style != null).forEach(elm => elm.style.height = '100%');
+ }
const doc = win.document;
- const attrs = {width, height};
+ const attrs = {width: width ?? '100%', height: height ?? '100%'};
if (adUrl && !ad) {
attrs.src = adUrl;
} else {
attrs.srcdoc = ad;
}
doc.body.appendChild(mkFrame(doc, attrs));
+ if (instl && win.frameElement) {
+ // interstitials are rendered in a nested iframe that needs to be sized
+ const style = win.frameElement.style;
+ style.width = width ? `${width}px` : '100vw';
+ style.height = height ? `${height}px` : '100vh';
+ }
}
}
diff --git a/creative/renderers/native/renderer.js b/creative/renderers/native/renderer.js
index 5cc8f100108..f7c124b41eb 100644
--- a/creative/renderers/native/renderer.js
+++ b/creative/renderers/native/renderer.js
@@ -45,6 +45,16 @@ function loadScript(url, doc) {
});
}
+function getRenderFrames(node) {
+ return Array.from(node.querySelectorAll('iframe[srcdoc*="render"]'))
+}
+
+function getInnerHTML(node) {
+ const clone = node.cloneNode(true);
+ getRenderFrames(clone).forEach(node => node.parentNode.removeChild(node));
+ return clone.innerHTML;
+}
+
export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) {
const {rendererUrl, assets, ortb, adTemplate} = nativeData;
const doc = win.document;
@@ -58,21 +68,32 @@ export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript)
return win.renderAd(payload);
});
} else {
- return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML));
+ return Promise.resolve(replacer(adTemplate ?? getInnerHTML(doc.body)));
}
}
export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) {
const {head, body} = win.document;
- const resize = () => sendMessage(MESSAGE_NATIVE, {
- action: ACTION_RESIZE,
- height: body.offsetHeight,
- width: body.offsetWidth
- });
+ const resize = () => {
+ // force redraw - for some reason this is needed to get the right dimensions
+ body.style.display = 'none';
+ body.style.display = 'block';
+ sendMessage(MESSAGE_NATIVE, {
+ action: ACTION_RESIZE,
+ height: body.offsetHeight,
+ width: body.offsetWidth
+ });
+ }
+ function replaceMarkup(target, markup) {
+ // do not remove the rendering logic if it's embedded in this window; things will break otherwise
+ const renderFrames = getRenderFrames(target);
+ Array.from(target.childNodes).filter(node => !renderFrames.includes(node)).forEach(node => target.removeChild(node));
+ target.insertAdjacentHTML('afterbegin', markup);
+ }
const replacer = getReplacer(adId, native);
- head && (head.innerHTML = replacer(head.innerHTML));
+ replaceMarkup(head, replacer(getInnerHTML(head)));
return getMarkup(adId, native, replacer, win).then(markup => {
- body.innerHTML = markup;
+ replaceMarkup(body, markup);
if (typeof win.postRenderAd === 'function') {
win.postRenderAd({adId, ...native});
}
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 00000000000..e683c044e71
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,248 @@
+const jsdoc = require('eslint-plugin-jsdoc')
+const lintImports = require('eslint-plugin-import')
+const neostandard = require('neostandard')
+const babelParser = require('@babel/eslint-parser');
+const globals = require('globals');
+const prebid = require('./plugins/eslint/index.js');
+const {includeIgnoreFile} = require('@eslint/compat');
+const path = require('path');
+const _ = require('lodash');
+
+function sourcePattern(name) {
+ return [`${name}/**/*.js`, `${name}/**/*.mjs`]
+}
+
+const sources = ['src', 'modules', 'libraries', 'creative'].flatMap(sourcePattern)
+const autogen = 'libraries/creative-renderer-*/**/*'
+
+const allowedImports = {
+ modules: [
+ 'crypto-js',
+ 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/
+ ],
+ src: [
+ 'fun-hooks/no-eval',
+ 'klona',
+ 'dlv',
+ 'dset'
+ ],
+ libraries: [],
+ creative: [],
+}
+
+function noGlobals(names) {
+ return {
+ globals: Object.entries(names).map(([name, message]) => ({
+ name,
+ message
+ })),
+ props: Object.entries(names).map(([name, message]) => ({
+ object: 'window',
+ property: name,
+ message
+ }))
+ }
+}
+
+function commonConfig(overrides) {
+ return _.merge({
+ plugins: {
+ jsdoc,
+ import: lintImports,
+ prebid
+ },
+ settings: {
+ jsdoc: {
+ tagNamePreference: {
+ return: 'return'
+ }
+ }
+ },
+ languageOptions: {
+ parser: babelParser,
+ sourceType: 'module',
+ ecmaVersion: 2018,
+ globals: {
+ BROWSERSTACK_USERNAME: false,
+ BROWSERSTACK_KEY: false,
+ FEATURES: 'readonly',
+ ...globals.browser
+ },
+ },
+ rules: {
+ 'comma-dangle': 'off',
+ semi: 'off',
+ 'space-before-function-paren': 'off',
+ 'import/extensions': ['error', 'ignorePackages'],
+
+ // 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.
+ //
+ // See Issue #1111.
+ // also see: reality. These are here to stay.
+
+ eqeqeq: 'off',
+ 'no-return-assign': 'off',
+ 'no-throw-literal': 'off',
+ 'no-undef': 2,
+ 'no-useless-escape': 'off',
+ 'no-console': 'error',
+ 'jsdoc/check-types': 'off',
+ 'jsdoc/no-defaults': 'off',
+ 'jsdoc/newline-after-description': 'off',
+ 'jsdoc/require-jsdoc': 'off',
+ 'jsdoc/require-param': 'off',
+ 'jsdoc/require-param-description': 'off',
+ 'jsdoc/require-param-name': 'off',
+ 'jsdoc/require-param-type': 'off',
+ 'jsdoc/require-property': 'off',
+ 'jsdoc/require-property-description': 'off',
+ 'jsdoc/require-property-name': 'off',
+ 'jsdoc/require-property-type': 'off',
+ 'jsdoc/require-returns': 'off',
+ 'jsdoc/require-returns-check': 'off',
+ 'jsdoc/require-returns-description': 'off',
+ 'jsdoc/require-returns-type': 'off',
+ 'jsdoc/require-yields': 'off',
+ 'jsdoc/require-yields-check': 'off',
+ 'jsdoc/tag-lines': 'off',
+ 'no-var': 'off',
+ 'no-empty': 'off',
+ 'no-void': 'off',
+ 'array-callback-return': 'off',
+ 'import-x/no-named-default': 'off',
+ 'prefer-const': 'off',
+ 'no-prototype-builtins': 'off',
+ 'object-shorthand': 'off',
+ 'prefer-regex-literals': 'off',
+ 'no-case-declarations': 'off',
+ 'no-useless-catch': 'off',
+ '@stylistic/quotes': 'off',
+ '@stylistic/quote-props': 'off',
+ '@stylistic/array-bracket-spacing': 'off',
+ '@stylistic/object-curly-spacing': 'off',
+ '@stylistic/semi': 'off',
+ '@stylistic/space-before-function-paren': 'off',
+ '@stylistic/multiline-ternary': 'off',
+ '@stylistic/computed-property-spacing': 'off',
+ '@stylistic/lines-between-class-members': 'off',
+ '@stylistic/indent': 'off',
+ '@stylistic/comma-dangle': 'off',
+ '@stylistic/object-curly-newline': 'off',
+ '@stylistic/object-property-newline': 'off',
+ '@stylistic/no-multiple-empty-lines': 'off',
+
+ }
+ }, overrides);
+}
+
+module.exports = [
+ includeIgnoreFile(path.resolve(__dirname, '.gitignore')),
+ {
+ ignores: [
+ autogen,
+ 'integrationExamples/**/*',
+ // do not lint build-related stuff
+ '*.js',
+ ...sourcePattern('plugins'),
+ ...sourcePattern('.github'),
+ ],
+ },
+ jsdoc.configs['flat/recommended'],
+ ...neostandard({
+ files: sources,
+ }),
+ commonConfig({
+ files: sources,
+ }),
+ ...Object.entries(allowedImports).map(([path, allowed]) => {
+ const {globals, props} = noGlobals({
+ require: 'use import instead',
+ ...Object.fromEntries(['localStorage', 'sessionStorage'].map(k => [k, 'use storageManager instead'])),
+ XMLHttpRequest: 'use ajax.js instead'
+ })
+ return commonConfig({
+ files: sourcePattern(path),
+ plugins: {
+ prebid,
+ },
+ rules: {
+ 'prebid/validate-imports': ['error', allowed],
+ 'no-restricted-globals': [
+ 'error',
+ ...globals
+ ],
+ 'no-restricted-properties': [
+ 'error',
+ ...props,
+ {
+ property: 'cookie',
+ object: 'document',
+ message: 'use storageManager instead'
+ },
+ {
+ property: 'sendBeacon',
+ object: 'navigator',
+ message: 'use ajax.js instead'
+ },
+ ...['outerText', 'innerText'].map(property => ({
+ property,
+ message: 'use .textContent instead'
+ })),
+ {
+ property: 'getBoundingClientRect',
+ message: 'use libraries/boundingClientRect instead'
+ },
+ ...['scrollTop', 'scrollLeft', 'innerHeight', 'innerWidth', 'visualViewport'].map((property) => ({
+ object: 'window',
+ property,
+ message: 'use utils/getWinDimensions instead'
+ }))
+ ]
+ }
+ })
+ }),
+ {
+ files: ['**/*BidAdapter.js'],
+ rules: {
+ 'no-restricted-imports': [
+ 'error', {
+ patterns: [
+ '**/src/events.js',
+ '**/src/adloader.js'
+ ]
+ }
+ ]
+ }
+ },
+ commonConfig({
+ files: sourcePattern('test'),
+ languageOptions: {
+ globals: {
+ ...globals.mocha,
+ ...globals.chai,
+ 'sinon': false
+ }
+ },
+ rules: {
+ // tests were not subject to many rules and they are now a nightmare
+ 'no-template-curly-in-string': 'off',
+ 'no-unused-expressions': 'off',
+ 'one-var': 'off',
+ 'no-undef': 'off',
+ 'no-unused-vars': 'off',
+ 'import/extensions': 'off',
+ 'camelcase': 'off',
+ 'no-array-constructor': 'off',
+ 'import-x/no-duplicates': 'off',
+ 'import-x/no-absolute-path': 'off',
+ 'no-loss-of-precision': 'off',
+ 'no-redeclare': 'off',
+ 'no-global-assign': 'off',
+ 'default-case-last': 'off',
+ '@stylistic/no-mixed-spaces-and-tabs': 'off',
+ '@stylistic/no-tabs': 'off',
+ '@stylistic/no-trailing-spaces': 'off'
+ }
+ })
+]
diff --git a/features.json b/features.json
index 4d8377cda7d..ee89ea6abaf 100644
--- a/features.json
+++ b/features.json
@@ -1,5 +1,6 @@
[
"NATIVE",
"VIDEO",
- "UID2_CSTG"
+ "UID2_CSTG",
+ "GREEDY"
]
diff --git a/gulpfile.js b/gulpfile.js
index 86c1b7fe509..608c9f67819 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -16,7 +16,6 @@ var helpers = require('./gulpHelpers.js');
var concat = require('gulp-concat');
var replace = require('gulp-replace');
var shell = require('gulp-shell');
-var eslint = require('gulp-eslint');
var gulpif = require('gulp-if');
var sourcemaps = require('gulp-sourcemaps');
var through = require('through2');
@@ -28,6 +27,7 @@ const {minify} = require('terser');
const Vinyl = require('vinyl');
const wrap = require('gulp-wrap');
const rename = require('gulp-rename');
+const run = require('gulp-run-command').default;
var prebid = require('./package.json');
var port = 9999;
@@ -48,7 +48,7 @@ function bundleToStdout() {
bundleToStdout.displayName = 'bundle-to-stdout';
function clean() {
- return gulp.src(['build'], {
+ return gulp.src(['build', 'dist'], {
read: false,
allowEmpty: true
})
@@ -79,23 +79,18 @@ function lint(done) {
if (argv.nolint) {
return done();
}
- const isFixed = function (file) {
- return file.eslint != null && file.eslint.fixed;
+ const args = ['eslint'];
+ if (!argv.nolintfix) {
+ args.push('--fix');
}
- return gulp.src([
- 'src/**/*.js',
- 'modules/**/*.js',
- 'libraries/**/*.js',
- 'creative/**/*.js',
- 'test/**/*.js',
- 'plugins/**/*.js',
- '!plugins/**/node_modules/**',
- './*.js'
- ], { base: './' })
- .pipe(eslint({ fix: !argv.nolintfix, quiet: !(typeof argv.lintWarnings === 'boolean' ? argv.lintWarnings : true) }))
- .pipe(eslint.format('stylish'))
- .pipe(eslint.failAfterError())
- .pipe(gulpif(isFixed, gulp.dest('./')));
+ if (!(typeof argv.lintWarnings === 'boolean' ? argv.lintWarnings : true)) {
+ args.push('--quiet')
+ }
+ return run(args.join(' '))().then(() => {
+ done();
+ }, (err) => {
+ done(err);
+ });
};
// View the code coverage report in the browser.
@@ -147,6 +142,17 @@ function makeVerbose(config = webpackConfig) {
});
}
+function prebidSource(webpackCfg) {
+ var externalModules = helpers.getArgModules();
+
+ const analyticsSources = helpers.getAnalyticsSources();
+ const moduleSources = helpers.getModulePaths(externalModules);
+
+ return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
+ .pipe(helpers.nameModules(externalModules))
+ .pipe(webpackStream(webpackCfg, webpack));
+}
+
function makeDevpackPkg(config = webpackConfig) {
return function() {
var cloned = _.cloneDeep(config);
@@ -163,14 +169,7 @@ function makeDevpackPkg(config = webpackConfig) {
.filter((use) => use.loader === 'babel-loader')
.forEach((use) => use.options = Object.assign({}, use.options, babelConfig));
- var externalModules = helpers.getArgModules();
-
- const analyticsSources = helpers.getAnalyticsSources();
- const moduleSources = helpers.getModulePaths(externalModules);
-
- return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
- .pipe(helpers.nameModules(externalModules))
- .pipe(webpackStream(cloned, webpack))
+ return prebidSource(cloned)
.pipe(gulp.dest('build/dev'))
.pipe(connect.reload());
}
@@ -183,14 +182,7 @@ function makeWebpackPkg(config = webpackConfig) {
}
return function buildBundle() {
- var externalModules = helpers.getArgModules();
-
- const analyticsSources = helpers.getAnalyticsSources();
- const moduleSources = helpers.getModulePaths(externalModules);
-
- return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
- .pipe(helpers.nameModules(externalModules))
- .pipe(webpackStream(cloned, webpack))
+ return prebidSource(cloned)
.pipe(gulp.dest('build/dist'));
}
}
@@ -337,6 +329,16 @@ function bundle(dev, moduleArr) {
.pipe(gulpif(sm, sourcemaps.write('.')));
}
+function setupDist() {
+ return gulp.src(['build/dist/**/*'])
+ .pipe(rename(function (path) {
+ if (path.dirname === '.' && path.basename === 'prebid') {
+ path.dirname = 'not-for-prod';
+ }
+ }))
+ .pipe(gulp.dest('dist'))
+}
+
// Run the unit tests.
//
// By default, this runs in headless chrome.
@@ -413,7 +415,9 @@ function runKarma(options, done) {
// the karma server appears to leak memory; starting it multiple times in a row will run out of heap
// here we run it in a separate process to bypass the problem
options = Object.assign({browsers: helpers.parseBrowserArgs(argv)}, options)
- const child = fork('./karmaRunner.js');
+ const child = fork('./karmaRunner.js', null, {
+ env: Object.assign({}, options.env, process.env)
+ });
child.on('exit', (exitCode) => {
if (exitCode) {
done(new Error('Karma tests failed with exit code ' + exitCode));
@@ -426,7 +430,15 @@ function runKarma(options, done) {
// If --file "" is given, the task will only run tests in the specified file.
function testCoverage(done) {
- runKarma({coverage: true, browserstack: false, watch: false, file: argv.file}, done);
+ runKarma({
+ coverage: true,
+ browserstack: false,
+ watch: false,
+ file: argv.file,
+ env: {
+ NODE_OPTIONS: '--max-old-space-size=8096'
+ }
+ }, done);
}
function coveralls() { // 2nd arg is a dependency: 'test' must be finished
@@ -521,7 +533,7 @@ gulp.task('build-bundle-dev', gulp.series('build-creative-dev', makeDevpackPkg(s
gulp.task('build-bundle-prod', gulp.series('build-creative-prod', makeWebpackPkg(standaloneDebuggingConfig), makeWebpackPkg(), gulpBundle.bind(null, false)));
// build-bundle-verbose - prod bundle except names and comments are preserved. Use this to see the effects
// of dead code elimination.
-gulp.task('build-bundle-verbose', gulp.series('build-creative-dev', makeWebpackPkg(makeVerbose(standaloneDebuggingConfig)), makeWebpackPkg(makeVerbose()), gulpBundle.bind(null, true)));
+gulp.task('build-bundle-verbose', gulp.series('build-creative-dev', makeWebpackPkg(makeVerbose(standaloneDebuggingConfig)), makeWebpackPkg(makeVerbose()), gulpBundle.bind(null, false)));
// public tasks (dependencies are needed for each task since they can be ran on their own)
gulp.task('test-only', test);
@@ -533,7 +545,9 @@ gulp.task(viewCoverage);
gulp.task('coveralls', gulp.series('test-coverage', coveralls));
-gulp.task('build', gulp.series(clean, 'build-bundle-prod', updateCreativeExample));
+// npm will by default use .gitignore, so create an .npmignore that is a copy of it except it includes "dist"
+gulp.task('setup-npmignore', run("sed 's/^\\/\\?dist\\/\\?$//g;w .npmignore' .gitignore", {quiet: true}));
+gulp.task('build', gulp.series(clean, 'build-bundle-prod', updateCreativeExample, setupDist, 'setup-npmignore'));
gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid));
gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test)));
diff --git a/integrationExamples/gpt/51DegreesRtdProvider_example.html b/integrationExamples/gpt/51DegreesRtdProvider_example.html
new file mode 100644
index 00000000000..7864f2e05f5
--- /dev/null
+++ b/integrationExamples/gpt/51DegreesRtdProvider_example.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+ 51Degrees RTD submodule example - Prebid.js
+
+
+
51Degrees RTD submodule - example of usage
+
+
div-banner-native-1
+
+
No response
+
+
+
+
div-banner-native-2
+
+
No response
+
+
+
+
+
Testing/Debugging Guidance
+
+
Make sure you have debug: true under pbjs.setConfig in this example code (be sure to remove it for production!)
+
Make sure you have replaced <YOUR RESOURCE KEY> in this example code with the one you have obtained
+ from the 51Degrees Configurator Tool
+
Open DevTools Console in your browser and refresh the page
+
Observe the enriched ortb device data shown below and also in the console as part of the [51Degrees RTD Submodule]: reqBidsConfigObj: message (under reqBidsConfigObj.global.device)