diff --git a/.eslintrc.js b/.eslintrc.js
index 184b042813d..5b69afa019f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -61,6 +61,7 @@ module.exports = {
'no-useless-escape': 'off',
'no-console': 'error',
'jsdoc/check-types': 'off',
+ 'jsdoc/no-defaults': 'off',
'jsdoc/newline-after-description': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param': 'off',
@@ -83,27 +84,52 @@ module.exports = {
files: key + '/**/*.js',
rules: {
'prebid/validate-imports': ['error', allowedModules[key]],
- 'prebid/no-innerText': ['error', allowedModules[key]],
'no-restricted-globals': [
'error',
{
name: 'require',
message: 'use import instead'
}
+ ],
+ 'prebid/no-global': [
+ 'error',
+ ...['localStorage', 'sessionStorage'].map(name => ({name, message: 'use storageManager instead'})),
+ {
+ name: 'XMLHttpRequest',
+ message: 'use ajax.js instead'
+ },
+ ],
+ 'prebid/no-member': [
+ 'error',
+ {
+ name: 'cookie',
+ target: 'document',
+ message: 'use storageManager instead'
+ },
+ {
+ name: 'sendBeacon',
+ target: 'navigator',
+ message: 'use ajax.js instead'
+ },
+ ...['outerText', 'innerText'].map(name => ({
+ name,
+ message: 'use .textContent instead'
+ }))
]
}
})).concat([{
// code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain.
files: 'plugins/*/**/*.js',
parser: 'esprima'
- },
- {
+ }, {
files: '**BidAdapter.js',
rules: {
'no-restricted-imports': [
'error', {
- patterns: ["**/src/events.js",
- "**/src/adloader.js"]
+ patterns: [
+ '**/src/events.js',
+ '**/src/adloader.js'
+ ]
}
]
}
diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml
index 21e7aadf97c..315fbb2ff09 100644
--- a/.github/workflows/jscpd.yml
+++ b/.github/workflows/jscpd.yml
@@ -101,7 +101,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}-L${dup.end - 1}`
+ 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;
@@ -109,7 +109,7 @@ jobs:
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. Keep up the great work! 🚀";
+ 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,
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
new file mode 100644
index 00000000000..034e0eddee7
--- /dev/null
+++ b/.github/workflows/linter.yml
@@ -0,0 +1,107 @@
+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: 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/creative/constants.js b/creative/constants.js
index d02c4c9d5e4..5f807c69f87 100644
--- a/creative/constants.js
+++ b/creative/constants.js
@@ -1,6 +1,7 @@
// eslint-disable-next-line prebid/validate-imports
-import { AD_RENDER_FAILED_REASON, EVENTS, MESSAGES } from '../src/constants.js';
+import {AD_RENDER_FAILED_REASON, EVENTS, MESSAGES} from '../src/constants.js';
+export {PB_LOCATOR} from '../src/constants.js';
export const MESSAGE_REQUEST = MESSAGES.REQUEST;
export const MESSAGE_RESPONSE = MESSAGES.RESPONSE;
export const MESSAGE_EVENT = MESSAGES.EVENT;
diff --git a/creative/crossDomain.js b/creative/crossDomain.js
index a851885bfc0..d3524f61d4b 100644
--- a/creative/crossDomain.js
+++ b/creative/crossDomain.js
@@ -1,9 +1,11 @@
import {
ERROR_EXCEPTION,
- EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED,
+ EVENT_AD_RENDER_FAILED,
+ EVENT_AD_RENDER_SUCCEEDED,
MESSAGE_EVENT,
MESSAGE_REQUEST,
- MESSAGE_RESPONSE
+ MESSAGE_RESPONSE,
+ PB_LOCATOR
} from './constants.js';
const mkFrame = (() => {
@@ -24,14 +26,27 @@ const mkFrame = (() => {
};
})();
+function isPrebidWindow(win) {
+ return !!win.frames[PB_LOCATOR];
+}
+
export function renderer(win) {
+ let target = win.parent;
+ try {
+ while (target !== win.top && !isPrebidWindow(target)) {
+ target = target.parent;
+ }
+ if (!isPrebidWindow(target)) target = win.parent;
+ } catch (e) {
+ }
+
return function ({adId, pubUrl, clickUrl}) {
const pubDomain = new URL(pubUrl, window.location).origin;
function sendMessage(type, payload, responseListener) {
const channel = new MessageChannel();
channel.port1.onmessage = guard(responseListener);
- win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]);
+ target.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]);
}
function onError(e) {
@@ -77,7 +92,7 @@ export function renderer(win) {
W.Promise.resolve(W.render(data, {sendMessage, mkFrame}, win)).then(
() => sendMessage(MESSAGE_EVENT, {event: EVENT_AD_RENDER_SUCCEEDED}),
onError
- )
+ );
});
win.document.body.appendChild(renderer);
}
diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html
index 0d0f63cbf1b..63842b00882 100644
--- a/integrationExamples/gpt/x-domain/creative.html
+++ b/integrationExamples/gpt/x-domain/creative.html
@@ -2,7 +2,7 @@
// creative will be rendered, e.g. GAM delivering a SafeFrame
// this code is autogenerated, also available in 'build/creative/creative.js'
-
+
+
+