userId: a filtered refreshUserIds must not release the auction early - #15
Open
MO-Thibault wants to merge 1242 commits into
Open
userId: a filtered refreshUserIds must not release the auction early#15MO-Thibault wants to merge 1242 commits into
MO-Thibault wants to merge 1242 commits into
Conversation
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…der fallback (prebid#15143) * Floxis Bid Adapter: read user-sync from response body (ext.sync), header fallback * Floxis Bid Adapter: select body.ext.sync entry matching enabled sync type * Floxis Bid Adapter: skip disabled-type body.ext.sync entry, keep header fallback getUserSyncs no longer emits a body.ext.sync entry whose type the publisher disabled (core userSync would reject it), and when the body carries only disabled-type entries it now falls through to the x-floxis-sync header instead of returning empty-handed. Tightens the round-2 selection fallback flagged by Codex review. * Floxis Bid Adapter: dedupe body and header user-syncs on the final URL getUserSyncs now keys its seen-set on the emitted sync URL for both the body.ext.sync and x-floxis-sync paths, so a sync delivered via the body on one response and via the header on another collapses to one instead of firing twice (the per-bidder sync budget burn flagged by Codex). Within a getUserSyncs call the two channels build the same URL for a given seat/region/consent, except for GPP strings where the server URL-encodes '~' as %7E and the client leaves it literal; that residual case is no worse than before the fix.
Bumps [undici](https://github.com/nodejs/undici) from 6.24.1 to 6.27.0. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](nodejs/undici@v6.24.1...v6.27.0) --- updated-dependencies: - dependency-name: undici dependency-version: 6.27.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Build: replace fs-extra with native node:fs * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Patrick McCann <pmccann@cafemedia.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* CI: harden Safari Technology Preview install * CI: run Safari Technology Preview tests on macOS 26 * fix wait-for-browserstack --------- Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
* Apply suggested fix to modules/smarthubBidAdapter.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to modules/smarthubBidAdapter.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to modules/smarthubBidAdapter.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Apply suggested fix to test/spec/libraries/greedy/greedyPromise_spec.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to test/spec/libraries/greedy/greedyPromise_spec.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Correct expected object declaration in test Fix typo in variable declaration for expected object. * Update test/spec/libraries/greedy/greedyPromise_spec.js Co-authored-by: derdeka <derdeka@users.noreply.github.com> * Simplify variable initialization in test Refactor variable declarations for actual and expected. --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Co-authored-by: derdeka <derdeka@users.noreply.github.com>
* add build on Windows * translate import paths * refactor workflow * stray param * fix define testing strategy job * force bash
* Build: replace fancy-log with gulplog * Build: replace fancy-log with gulplog * Add comment about gulplog availability --------- Co-authored-by: Patrick McCann <pmccann@cafemedia.com>
* New adapter: billow_rtb25 * delete options * add public interface * Change the adapter type to a ts file * update Adapter billow_rtb25: Add support for the sharedId field * update Adapter billow_rtb25: Add support for the sharedId field * update Adapter billow_rtb25: fix * fix --------- Co-authored-by: zepeng.yin <zp.yin@foxmail.com>
* ConnectAd: adapter, documentation and test updates Co-authored-by: Cursor <cursoragent@cursor.com> * ConnectAd: use CDN URL for outstream renderer Co-authored-by: Cursor <cursoragent@cursor.com> * ConnectAd: restore EIDs mapping and stabilize audio spec in no-feature runs Co-authored-by: Cursor <cursoragent@cursor.com> * Fix missing semicolon on line 35 in connectadBidAdapter_spec.js * Add missing tests for code coverage: viewability, outstream renderer, floorprice fallback, endpointUrl override, and video/native media type detection * Add ConnectAd adapter tests to restore PR code coverage. Cover viewability, outstream renderer, media type fallbacks, native asset alignment, and getUserSyncs edge cases that Barecheck reported as uncovered. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix ConnectAd spec for lint and feature-disabled CI. Remove unnecessary Function.prototype.call usage flagged by ESLint and skip video viewability coverage when the VIDEO feature is disabled. Co-authored-by: Cursor <cursoragent@cursor.com> * ConnectAd Adapter: address requested review changes * ConnectAd Adapter: address follow-up review feedback Remove dead bidRequest.data string parsing incompatible with ortbConverter object identity, refactor outstream tests with sinon sandbox cleanup, and drop obsolete string-parse specs. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Robert Ray Martinez III <rrmartinez1552@gmail.com>
* Yieldmo Bid Adapter: revert badv support (keep bcat) Reverts the badv portions of prebid#14989 because a large publisher's long badv lists are materially hurting monetization (FS-12411). bcat is left fully intact (ortb2 + params merge on banner and video). badv is restored to its exact pre-prebid#14989 behavior: - banner GET no longer sends badv - video OpenRTB sends badv: bidRequests[0].params.badv || [] (no ortb2/merge) - params.badv array validation moves back into validateVideoParams (video-only) Docs and unit tests updated to match; getBlocklist retained for bcat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Yieldmo Bid Adapter: document video-only params.badv Restore badv to the docs after the FS-12411 revert, but scoped to its actual reverted behavior: video-only, params source only (no ortb2, no banner). Documentation only; no code change. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Prebid Server Adapter: align sync browser restrictions * Prebid Server Adapter: align sync browser restrictions ### Motivation - PBS cookie syncs were being attempted in browsers where regular user syncs are blocked, causing inconsistent behavior and potential failures. - The change centralizes the browser/cookie eligibility logic so both client-side user syncs and PBS syncs share the same restrictions (avoid Chrome on iOS, Safari, and Firefox and require cookies enabled). ### Description - Exported `browserSupportsUserSyncCookies()` from `src/userSync.ts` and switched the user-sync getter to call it so the eligibility check is reusable. - Added an early-return in `queueSync` inside `modules/prebidServerBidAdapter/index.ts` to skip PBS cookie syncs when `browserSupportsUserSyncCookies()` is false. - Updated `src/userSync.ts` to use the new exported helper for `browserSupportsCookies` so logic is not duplicated. - Added unit tests in `test/spec/modules/prebidServerBidAdapter_spec.js` to verify PBS syncs are skipped for Safari, Firefox, and Chrome on iOS while the auction request still proceeds. ### Testing - Ran lint for the changed files with `npx eslint modules/prebidServerBidAdapter/index.ts src/userSync.ts test/spec/modules/prebidServerBidAdapter_spec.js --cache --cache-strategy content` which completed successfully. - Executed the adapter test target with `npx gulp test --nolint --file test/spec/modules/prebidServerBidAdapter_spec.js` and the spec chunk completed successfully (tests passed). - Confirmed the modified spec asserts that PBS sync requests are not made on Safari, Firefox, and Chrome on iOS (unit tests passed). * Prevent user syncs on browsers with restricted cookies (Safari/Firefox/Chrome iOS) ### Motivation - Prevent initiating user syncs when the browser environment is known to block third-party cookies or when cookies are disabled to avoid futile sync attempts and privacy/regulatory issues. - Centralize the browser/cookie capability check so both the PBS adapter and the user sync subsystem use the same logic. ### Description - Added `browserSupportsUserSyncCookies()` in `src/userSync.ts` which returns `!isSafariBrowser() && !isFirefoxBrowser() && !isChromeIOSBrowser() && storage.cookiesAreEnabled()`. - Updated `userSync` to use the new `browserSupportsUserSyncCookies()` via the `browserSupportsCookies` getter to keep cookie-capability checks consistent. - Imported and used `browserSupportsUserSyncCookies()` in `modules/prebidServerBidAdapter/index.ts` to early-return from `queueSync()` when user sync cookies are not supported. - Extended and updated unit tests in `test/spec/modules/prebidServerBidAdapter_spec.js` to stub browser detectors and assert that PBS syncs are not requested for Safari, Firefox, and Chrome on iOS. ### Testing - Ran unit tests for the s2s/PBS adapter spec (`test/spec/modules/prebidServerBidAdapter_spec.js`) including new browser-restriction cases, and they passed. - Ran the repository unit test suite after changes and all tests succeeded. * better test fix --------- Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* New Adapter: m152 * fix adapter according to new example --------- Co-authored-by: 152media <info@152media.com>
…prebid#15148) on * new version * fixes * naming fixes * test fix * copilot cr fixes * linter fix * fix url documentation
) * Uniquest Adapters: send im_uid from intimatemerger EID * Uniquest Adapters: fix im_uid EID lookup to use userIdAsEids Read EIDs from bidRequest.userIdAsEids (the canonical path populated by the Prebid userId module) with ortb2.user.ext.eids as a fallback for manually configured cases. Previously, only the ortb2 path was checked, causing im_uid to be silently omitted when the intimatemerger submodule was configured via the userId module. Extracted shared getImuid() helper into uniquestUtils to deduplicate the logic across both adapters. Added tests covering the userIdAsEids path. * Uniquest Adapters: fix im_uid lost when userIdAsEids is empty array
…ebid#15187) * Implemented setting device.connectiontype Bid Request property ADBRO-4534 * Avoided override existing Device Connection Type ADBRO-4534
* Remove GVLID from bmsBidAdapter.js * Remove unused GVLID constant from bmsBidAdapter_spec.js Removed GVLID constant from the test file as it was not used in the tests. * Remove GVLID reference in ortbRequestData and networkId
…5261) * Build system: update ES5 build to do a second pass of babel * usage, not entry * whitespace * es5 pass on creative source * try testing es5 on pipeline * typo * try transform-runtime && disable caching * use core-js-pure * extract common webpack config * adjust settings * adjust concurrency * run e2e tests for es5 * fix wdio maxInstances * move es5 testing up a level * fix needs * fix input * remove es5 testing * remove core-js-pure * separate corejs polyfills to their own chunk * Revert "remove core-js-pure" This reverts commit b952156. * Revert "remove es5 testing" This reverts commit 824cdda. * Core: guard isSecureContext access (prebid#15368) * Reapply "remove es5 testing" This reverts commit 5eae4a0. * Reapply "remove core-js-pure" This reverts commit debfb17. * Build system: remove leftover BROWSERS_JSON plumbing The ES5 test experiment added a `browsers-json` input to run-tests.yml and threaded it through to karma/wdio as BROWSERS_JSON. When the experiment was removed, the input definition (and its `default: 'browsers.json'`) went away but the `env:` entry that reads it did not. An undefined reusable-workflow input evaluates to the empty string rather than being unset, and `process.env.BROWSERS_JSON ?? 'browsers.json'` only falls back on null/undefined -- so the empty string reached `require('./')`, which resolves package.json `main` and throws MODULE_NOT_FOUND. Karma died while building its config, before webpack started, failing every Browserstack unit test chunk in ~400ms. Drop the dead plumbing and restore the original browsers.json requires. This also restores the wdio browser filter, which was collateral damage of the same edit: without it `gulp e2e-test` (non-local) targeted every entry in browsers.json, including the Safari entries that are excluded on purpose because `browser.url()` times out on them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Build system: declare babel-plugin-polyfill-corejs3 explicitly plugins/polyfills.js does `require('babel-plugin-polyfill-corejs3')` but the package was never a declared dependency. It only resolved because npm hoisted a copy to the top level to satisfy @babel/plugin-transform-runtime, so which version first-party code loaded -- or whether it resolved at all -- was a side effect of hoisting. That already broke once on this branch: removing an unrelated devDependency left both copies nested (under plugin-transform-runtime and preset-env) with nothing at the top level, so `gulp build --polyfills` failed with "Cannot find module 'babel-plugin-polyfill-corejs3'" and took the whole Build metadata workflow with it. Declare ^0.13.0, which is what npm ci already resolves today, so the dependency graph is unchanged -- the top-level copy stays 0.13.0 and preset-env keeps its nested 0.14.2 -- but is now guaranteed by an explicit root dependency instead of hoisting luck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tests: make e2e frame switching work on legacy WebDriver dialects webdriverio's `switchFrame(element)` posts the entire element object as the frame locator on classic WebDriver. Modern drivers accept it; the chromedriver 2.x that ships with older BrowserStack images rejects it outright with "Unsupported frame locator: java.util.HashMap", so every `should render GAM creative` test failed for harness reasons rather than telling us anything about the code under test. Switch by frame index instead when the session is not W3C - an index is the one frame locator every dialect understands. Modern browsers keep the existing element-based path, so behaviour there is unchanged. Also drop the browserstack `testReporting` block: it has never successfully bootstrapped in any run (`failed to bootstrap TypeError: Cannot read properties of undefined (reading 'automate')`, followed by a stream of gRPC ECONNREFUSED to the sdk-platform socket), and the noise obscures real failures. Verified: full local e2e suite on Chrome 137 passes unchanged (7 passed, 1 skipped), and on BrowserStack Chrome 50 the frame-locator error is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CI: run e2e tests against the ES5 bundle on Chrome 50 Adds an "ES5 E2E tests" job that builds with --ES5 and drives the e2e suite on Chrome 50 - the oldest browser webpack.common.js claims to target - through BrowserStack. e2e rather than unit tests: gulp starts karma via `fork('./karmaRunner.js', null, {env})`, which forwards env but not argv, so `--ES5` never reaches karma's webpack config. `gulp test-only-nobuild --ES5` therefore builds an ordinary bundle, and the earlier attempts to unit-test ES5 on Safari 10 were only ever testing un-transpiled code. The e2e path takes its bundle from the separate `gulp build --ES5` job, so what it loads really is ES5. BROWSERS_JSON selects the browser set. Note the `||` in wdio.conf.js rather than `??`: an omitted reusable-workflow input arrives as the empty string, not as undefined, which is what previously turned this into a crash. This check does not pass yet, and the failure is real rather than a harness artifact: on Chrome 50 the ES5 bundle loads and initialises correctly (pbjs v11.29.0-pre, GPT ready, creative iframe rendered), but no auction can complete because src/ajax.ts:66 calls `new Headers(options.customHeaders)` and old Blink rejects an explicit `undefined` there ("Failed to construct 'Headers': No matching constructor signature"), so every adapter throws while building its request. `new AbortController()` (src/ajax.ts:12) is the next wall. core-js polyfills ECMAScript built-ins and a small curated set of web.* APIs, but has no module for the fetch family at all, so `useBuiltIns: 'usage'` cannot supply these - fixing them is a separate decision about the real support floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Build system: polyfill the ES5 build with core-js-pure instead of global core-js `useBuiltIns: 'usage'` injects bare `require("core-js/modules/..")` statements whose only purpose is their side effect. Those land in the precompiled `dist/src/**` files, which package.json's `sideEffects` allowlist declares pure, so webpack is free to drop them - and it does. The result was polyfills that ship but never install: measured on BrowserStack Chrome 50, five builtins were present in the emitted corejs chunk yet absent at runtime, while others from the same chunk applied fine: String.prototype.replaceAll (24 first-party call sites) String.prototype.padStart (5) Object.hasOwn (2) Promise.allSettled (2) Array.prototype.at `method: 'usage-pure'` instead rewrites each call site to reference an imported implementation, so the polyfill is a value dependency that cannot be dropped without breaking the code. It also stops patching the publisher's globals, which is what a library embedded in someone else's page should be doing anyway. This restores the approach from the abandoned 6da26de / cdccacd attempt, including the `@babel/runtime-corejs3` dependency it needed: with transform-runtime in the pipeline, the polyfill provider routes its imports through that package rather than core-js-pure directly. Verified on the full ES5 build: - core-js/modules (droppable global patches): 189 -> 0 - core-js-pure modules (value imports): 0 -> 685 - all 1576 emitted chunks still parse as ES5 - Chrome 50: prebid initialises, GAM creative renders, and none of the six probed builtins is patched onto a global - call sites carry their own - served bundle is 13KB *smaller* (559,437 -> 546,297 bytes) - local e2e suite on Chrome 137 unchanged: 7 passed, 1 skipped Note this does not help the remaining Chrome 50 / Safari 10 blocker, which is host APIs rather than ECMAScript builtins: core-js-pure has no fetch family either, so `new Headers(undefined)` and `AbortController` still fail. Also note polyfill coverage can no longer be checked by feature-detecting globals; in pure mode a working polyfill is invisible from outside the bundle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Build system: drop IE from the ES5 build's target list No version of Internet Explorer ever shipped Proxy - caniuse records "n" for every release from 5.5 to 11, and it cannot be polyfilled (neither core-js nor core-js-pure has a module for it, because arbitrary property interception is not expressible in ES5). Prebid uses `new Proxy` in six places including src/adapters/bidderFactory.ts, on every bidder's path, and the objectGuard / ortbGuard consent layer. IE support was therefore never achievable, and there is no "raise the minimum IE version" option either - Edge 12 is the oldest Microsoft browser with Proxy, and Edge 79+ is Chromium, which `chrome >= 50` already covers. Listing it was not free: it is an input to polyfill selection, so it inflated the bundle for every consumer in exchange for support that cannot work. Note that `browsers` could not simply be shortened: it also drove preset-env's syntax decisions, and every remaining target supports ES2015, so removing IE silently stopped the ES5 build from emitting ES5 - measured at 727 of 1576 chunks containing ES2015+ syntax. Dropping `targets` entirely would be worse still, since package.json's `browserslist` ("> 0.25%") would then apply. preset-env now uses `forceAllTransforms` for the syntax floor, leaving `browsers` to drive polyfills alone. That separation is only possible because polyfills moved to the standalone polyfill-corejs3 plugin in the previous commit. Verified: - all 1576 emitted chunks still parse as ES5 - core-js-pure modules 685 -> 608, served bundle 546,297 -> 534,061 bytes - Chrome 50 unchanged: prebid initialises, creative renders, same event sequence, still exactly one error (the unrelated `Headers` host-API one) - local e2e suite on Chrome 137: 7 passed, 1 skipped Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Build system: ponyfill the fetch family for targets without AbortController core-js does not cover the Fetch standard, so `polyfill-corejs3` cannot supply it, and prebid's ajax layer was unusable on the oldest browsers the ES5 build claims to support: `new Headers(options.customHeaders)` threw on Chrome 50 and `new AbortController()` was undefined on both Chrome 50 and Safari 10, so every adapter died while building its request and no auction could complete. Fills the gap with a provider built on @babel/helper-define-polyfill-provider - the same machinery babel-plugin-polyfill-corejs3 is itself built on - so it inherits the three properties that matter: - target driven: `shouldInjectPolyfill` runs the provider's compat data through babel's target resolution, so raising the target list removes the rewrites, the ponyfill, and whatwg-fetch from the output entirely (verified: 0 modules in the webpack graph at chrome >= 66 / firefox >= 57 / safari >= 12.1). - `usage-pure`: references are rewritten to local imports, so `window.fetch` is left alone. whatwg-fetch's own global install is `if (!g.fetch)`, and every remaining target has fetch, so it is inert too. - usage driven: only the APIs actually referenced are pulled in. The whole family moves together deliberately. A ponyfilled `Headers` or `signal` cannot be handed to the *native* fetch, which reads them through internal slots: measured on these browsers, a foreign `Headers` is either rejected ("Failed to construct 'Request': Invalid value", Chrome 50) or silently emptied (Safari 10), and a foreign `signal` is silently discarded, because `signal` was not a member of `RequestInit` before Chrome 66 / Safari 12.1. So AbortController - the newest of the group - decides for all of them. Being XHR-backed, whatwg-fetch also gives real cancellation via `XMLHttpRequest.abort()`. Native fetch could not be cancelled at all between Chrome 42 and 66; `response.body.cancel()` only helps once headers have arrived, which is too late for a bid timeout. Result on BrowserStack Chrome 50, basic_banner_ad.spec.js: 0 passing -> 2 passing. The bid request now reaches the server (2x POST /appnexus, previously none) and targeting resolves (`hb_format: 'banner'`, previously `{}`). Zero occurrences of the Headers or AbortController errors remain. The two still-failing cases are `should render GAM creative`, waiting on a GAM creative iframe that never appears - Google Publisher Tag's own rendering on a 2016 browser, downstream of anything prebid controls. Also verified: all 1577 chunks still parse as ES5, and the local e2e suite on Chrome 137 is unchanged at 7 passed / 1 skipped. One operational note: webpack.conf.js's filesystem cache does not key on these targets, so `.cache` must be cleared after editing `browsers` or a stale graph is reused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tests: fix e2e frame switching on legacy drivers (use element refs, not indexes) Two harness bugs that together made the ES5 e2e run look like a rendering failure on Chrome 50. First, the frame locator. webdriverio posts its entire element object as the frame id, which legacy JSONWire drivers reject with "Unsupported frame locator: java.util.HashMap". The previous commit worked around that with a frame *index*, computed by matching `contentWindow` against `window.frames` - which every dialect accepts. But chromedriver 2.x does not order frames the way `window.frames` does, so a correctly computed index quietly selects a different document. Measured on Chrome 50, with the GAM iframe genuinely at index 3: switchToFrame(3) -> about:srcdoc, body length 0 switchToFrame({ELEMENT: id}) -> about:blank, body length 37512, 1 nested iframe, 1 matching iframe[srcdoc] So it had been switching into the wrong frame and reporting the creative as missing. The legacy `{ELEMENT: id}` reference is what these drivers want; the W3C `element-6066-..` key is rejected just like the full object. Second, nothing ever returned the driver to the top-level document. `setupTest` sets `this.retries(..)` but `before` only runs once, so once a test switched into the creative iframe every retry searched for a top-level element from inside it and failed with "still not existing" whatever the original cause was. Any transient first-attempt miss became a permanent three-attempt failure. The reset is scoped to legacy drivers: on W3C sessions webdriverio's bidi layer tracks the active context itself and an explicit switch to the top makes it report "execution contexts cleared" and lose the frame. Prebid was never at fault here. `pbjs.getEvents()` on Chrome 50 reports adRenderSucceeded with no adRenderFailed, and the BrowserStack console log for the session contains 54 messages, all info level, with no errors or warnings. basic_banner_ad.spec.js on BrowserStack Chrome 50: 4 passing, 0 failing (was 0 passing, 4 failing). Local suite on Chrome 137 unchanged: 7 passed, 1 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CI: cover every ES5 target in the ES5 e2e browser list webpack.common.js declares chrome >= 50, firefox >= 50 and safari >= 10, but the e2e job only exercised the Chrome floor. Add the oldest BrowserStack build of each, so the list matches what the build claims to support. Versions are what BrowserStack actually offers at each floor: chrome 50.0 and firefox 50.0 on Windows 10, and safari 10.1 on OS X Sierra - Safari is tied to the OS, and Sierra/10.1 is the only 10.x image available. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tests: return to the top frame before navigating in e2e helpers `should render GAM creative` descends into the creative's iframe and stays there. On legacy drivers `browser.url` then reloads *that frame* instead of the top window, so the next suite ran entirely in the wrong document: on Safari 10.1 the second `should load the targeting keys` failed with A JavaScript exception occured: undefined is not an object (evaluating 'window.pbjs.getAdserverTargeting') because `window.pbjs` does not exist inside the creative. Chrome 50's driver happens to reset context on navigation, which is why only Safari showed it. `topFrame()` is already a no-op on W3C sessions, so this only affects the legacy drivers that need it. basic_banner_ad.spec.js on BrowserStack safari 10.1: 4 passing (was 3 passing, 1 failing). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CI: run the firefox ES5 e2e on 65, the oldest version GPT renders on Firefox 50 is a legitimate polyfill target but cannot run this suite: GPT never reaches apiReady there, so no slot is ever displayed, nothing renders, and the `googletag.cmd` queue the test page's bidsBackHandler pushes into is never drained. Prebid itself is unaffected - the auction and targeting tests pass on firefox 50 - so the build target stays at >= 50 and only the browser under test moves. Bisected on BrowserStack, checking `googletag.apiReady`, defined slot count and the presence of the creative iframe: 50 apiReady false, no slots 52 .. 64 apiReady true, 1 slot, but no creative iframe ever appears 65 and later creative iframe renders so 65 is the floor. Noted next to the target in webpack.common.js so the gap between "we polyfill for this" and "we can e2e test this" is not lost. basic_banner_ad.spec.js on BrowserStack firefox 65: 4 passing (was 2 passing, 2 failing on 50). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "drop the browserstack testReporting config" Restores the block removed in fd75fa5. That removal was wrong on two counts. `testReporting` / `testReportingOptions` are not mistaken option names - the service maps them to `testObservability` / `testObservabilityOptions` itself: if (!isUndefined(_options.testReporting)) { _options.testObservability = _options.testReporting; } And removing them never disabled observability, which is on by default; it only discarded the values run-tests.yml exports through the browserstack setup-env and setup-local actions. Without them `projectName` and the tunnel identifier reach nothing, so CI sessions show up unnamed and webdriverio opens a second BrowserStackLocal tunnel beside the one setup-local already started. The bootstrap failure quoted as justification ("failed to bootstrap TypeError: Cannot read properties of undefined (reading 'automate')", then gRPC ECONNREFUSED on /tmp/sdk-platform-*.sock) cannot have been caused by this config, and is not fixed by removing it. It looks environmental - the SDK's helper process failing to reach BrowserStack's config/observability endpoints from a CI runner - and it is non-fatal; the tests ran through it. Locally the block is restored and there are no bootstrap errors at all. basic_banner_ad.spec.js still passes on all three ES5 browsers with this restored: chrome 50.0, firefox 65.0, safari 10.1, 4 passing each. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tests: bind browserstack sessions to our own local tunnel explicitly Nothing in the capabilities said which BrowserStackLocal tunnel a session should use. `browserstackLocal: true` is supposed to have the service inject that, but its SDK bootstrap fails on CI runners ("failed to bootstrap ... reading 'automate'", then gRPC ECONNREFUSED on /tmp/sdk-platform-*.sock), and a session with no binding is free to attach to any tunnel open on the account - including one belonging to a concurrent run. That is not hypothetical. In run 30960151279 the chrome 50 and firefox 65 sessions loaded the page and passed, while the safari 10.1 session in the same build produced *no console output whatsoever* ("No messages were logged in this Session", against 147KB from a passing chrome session), then failed with `undefined is not an object (evaluating 'window.pbjs.getAdserverTargeting')` on the very first test - pbjs was missing because the page had never loaded. The same suite passes on safari 10.1 locally, where only one tunnel exists and the routing cannot be ambiguous. So set `local` and `localIdentifier` in bstack:options directly, and stop opening a second tunnel when run-tests.yml has already started one via setup-local. Also sets projectName, which was reaching nothing: testReportingOptions applies to observability, not to the Automate session, so sessions were filed under "Untitled Project" despite BROWSERSTACK_PROJECT_NAME being exported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tests: print a findable summary of e2e failures A failing browserstack e2e run is around ten thousand lines, of which about eighty say anything about the tests, and webdriverio reports each spec as its worker finishes - so the failures end up scattered through the log rather than collected anywhere. Working out what actually broke means reading the whole thing. Collect failures per worker and print one block, searchable as "E2E FAILURES", naming each failed test and its error. Written with a single console.log because the browserstack service logs asynchronously on the same stream and will interleave itself between separate calls. Keyed by test, since mocha invokes afterTest once per retry and the interesting thing is the outcome, not each attempt. Under GitHub Actions it also emits ::error annotations, so failures appear on the run summary instead of only deep inside the step log. Log verbosity is left alone: the noise is not the problem, the absence of a summary was. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix Request url normalization and keepalive in the fetch ponyfill Found by running ajax_spec against the ES5 bundle - the e2e suite passes with these defects present, which is a fair argument for the unit tests existing. whatwg-fetch keeps whatever url it was handed, where native Request normalizes per the URL spec, so `https://www.example.com` stayed un-normalized instead of becoming `https://www.example.com/`; and whatwg-fetch does not implement keepalive at all, so `request.keepalive` read back as undefined rather than a boolean. src/ajax.ts reads both off the request, so the ES5 build was quietly behaving differently from every other build. Normalize through `URL` (which the usage-pure pass supplies on old browsers) and round-trip keepalive as a boolean. No browser this ponyfill targets supports keepalive even natively - XHR cannot honour it - but the value still has to survive for callers that check it. ajax_spec with --ES5: 7 failures -> 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CI: key the browserstack concurrency group by test command Every browserstack job in a run shared the group 'browserstack-<run_id>', and github only keeps one job queued per group - anything beyond that is cancelled, not queued, as the existing comment here notes. Adding the eight ES5 unit chunks therefore killed the ES5 e2e job outright: cancelled after 80 seconds with no log blob, having never reached a step, in run 30962273799. Including test-cmd gives each suite its own queue. This does let different suites run at the same time, but the account's parallel session limit is not what the group was protecting: the wait-for-browserstack step polls plan.json and blocks until enough sessions are free, independently of any concurrency group. This restores 72264c5, which made exactly this change while ES5 testing was first being attempted, and which 824cdda reverted along with everything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CI: drop safari from the ES5 browser set Safari 10.1 fails for a reason that is not the ES5 bundle: the session loads no page at all - browserstack records "No messages were logged in this Session" against 147KB of console output from a passing chrome session in the same build - and the same suite passes on safari 10.1 locally, including under CI's exact tunnel topology. Chrome 50 and firefox 65 both pass in CI. Left out rather than leaving the branch red on something unrelated to what is being tested. See ~/tasks/es5-e2e-safari-failure.md for what has been ruled out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CI: clarify why the browserstack concurrency group is keyed by command Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tests: exclude the triplelift e2e spec on firefox 65 The spec added in prebid#15191 fails on firefox 65, the oldest firefox the --ES5 build targets, and the cause is the creative rather than the ES5 bundle: prebid reports bidWon and adRenderSucceeded, and the nested ad iframe is created but left empty. triplelift's creative script - fixtured as test/fake-server/static/triplelift-ttj.js so the test does not break when they change it - fills that iframe with document.open()/write(). Prebid renders creatives inside an iframe[srcdoc], and in a document whose ancestor is about:srcdoc, firefox before 66 throws NS_ERROR_MALFORMED_URI from document.open(): it re-resolves the document URI and cannot parse about:srcdoc as a base. Verified directly on firefox 65 - contentWindow is fine, and the identical markup renders when handed to the iframe as srcdoc. So triplelift bids genuinely would not render on firefox 65. Excluding the spec for that browser records the limitation rather than adjusting the fixture to hide it. Chrome 50 runs the spec and passes. Keyed by browser@version, so the modern matrix is untouched. Also drops a claim in the neighbouring comment that an empty browserstack console log meant a session never reached the local server. Browserstack captures console logs on chrome only: firefox 65 sessions report "No messages were logged in this Session" even when they pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Patrick McCann <patmmccann@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removed gvlid property from the spec object.
* Geoedge RTD Provider: outstream video monitoring Video creatives are VAST rather than HTML, so there is no markup to wrap. With the new `outstream` param the module wraps the bid's own renderer instead and asks the client whether the creative may run. If the client does not load in time the creative renders anyway — an ad is never lost because monitoring was unavailable. Bids carrying a safeRenderer are skipped, since prebid loads that renderer's own script and never calls bid.renderer. getInitialParams also carries `pbjs: getGlobal()` into the preloaded frame, so the client gets a direct handle instead of resolving the global by name via `_pbjsGlobals`. The frame is same-origin, so this grants no new capability — it only removes the lookup. Restores the `export` keywords the spec relies on, adds JSDoc, and extends the spec from 17 to 46 tests covering detection, wrapping, render-time enforcement, parked renders and the fail-open deadline. * Honor params.bidders on the outstream gate path * Adopt the naming from prebid#15262 * Only pass the prebid instance when outstream is enabled * Geoedge RTD Provider: gate each bid independently on a shared renderer Adapters may reuse one Renderer across bids (ozone caches one at module scope). The gate wrapped it once and closed over the first bid, so later bids were judged by the first creative: a clean bid could be dropped, a blocked one released. The parked-render path stored the captured bid too. gateOutstreamRender now takes the renderer and reads the bid from the render arguments, which executeRenderer has passed first since v8. isVastBid moved up to onBidResponse, so a VAST bid whose renderer is already gated no longer falls through to the display wrapper. params.bidders is now passed to the client, so the opt-out covers its inspection. Seven tests for a shared renderer; five fail if the capture is reinstated.
* feat: add SuperEdge * feat: Add SuperEdge Adapter * fix: fix bidder code case & add multi-region support * fix:sk routing, add type definitions, and improve coverage
* Remove GVLID constant from mediafuseBidAdapter Removed GVLID constant from mediafuseBidAdapter. * Remove GVLID from revnewBidAdapter Removed GVLID constant from revnewBidAdapter.
…rl/burl win (prebid#15506) * add deferredBilling support using onBidBillable * update burl setting * support nurl firing logic * add extra signals to taboola request * add extra ad signals * fix missing semicolon * use Prebid's built-in counters * updated detectBot logic * Taboola Bid Adapter: prioritize ortb2 site.content.language over navigator.language * Taboola Bid Adapter: fire nurl and burl as separate win and billing notices --------- Co-authored-by: ronishefi <168074830+ronishefi9@users.noreply.github.com>
Bumps [@grpc/grpc-js](https://github.com/grpc/grpc-node) to 1.13.5 and updates ancestor dependency [@wdio/browserstack-service](https://github.com/browserstack/wdio-browserstack-service/tree/HEAD/packages/browserstack-service). These dependencies need to be updated together. Updates `@grpc/grpc-js` from 1.13.3 to 1.13.5 - [Release notes](https://github.com/grpc/grpc-node/releases) - [Commits](https://github.com/grpc/grpc-node/compare/@grpc/grpc-js@1.13.3...@grpc/grpc-js@1.13.5) Updates `@wdio/browserstack-service` from 9.33.1 to 9.35.0 - [Release notes](https://github.com/browserstack/wdio-browserstack-service/releases) - [Changelog](https://github.com/browserstack/wdio-browserstack-service/blob/main/packages/browserstack-service/CHANGELOG.md) - [Commits](https://github.com/browserstack/wdio-browserstack-service/commits/@wdio/browserstack-service@9.35.0/packages/browserstack-service) --- updated-dependencies: - dependency-name: "@grpc/grpc-js" dependency-version: 1.13.5 dependency-type: indirect - dependency-name: "@wdio/browserstack-service" dependency-version: 9.35.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Apply suggested fix to test/spec/modules/msftBidAdapter_spec.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to test/spec/modules/msftBidAdapter_spec.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to test/spec/modules/msftBidAdapter_spec.js from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Apply suggested fix to modules/geoedgeRtdProvider.md from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to modules/geoedgeRtdProvider.md from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Apply suggested fix to modules/geoedgeRtdProvider.md from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Build system: exclude dead browsers from browserslist IE 11 sits at 0.266% global usage, just above the `> 0.25%` threshold, so a routine caniuse-lite data refresh (1.0.30001807 -> 1.0.30001810, picked up by `gulp update-browserslist` during the 11.30.0 release) pulled it into the resolved preset-env targets. Everything babel touches is then downleveled to ES5: `@babel/plugin-transform-runtime` starts injecting regenerator/asyncToGenerator helpers into the default bundle, and the CommonJS e2e specs gain ESM helper imports, which makes node load them as ES modules and fail on `require is not defined in ES module scope`. ES5 output is the job of the separate `--ES5` build, which forces syntax down independently of this list (see webpack.common.js), so the support list has no reason to carry browsers that are no longer maintained. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Test suite: stop running babel over the e2e specs `mochaOpts.compilers` is a mocha 6 option that webdriverio still honors by importing the named module, so every e2e spec was passing through @babel/register and the repo's babel config. Nothing in test/spec/e2e needs it - the specs are plain CommonJS running under node - and it makes them fragile: as soon as preset-env's targets require helpers, @babel/plugin-transform-runtime injects ESM `import` statements into those CommonJS files, node then loads them as ES modules, and every spec dies on `require is not defined in ES module scope` before a single test runs. Dropping the compiler also retires the BABEL_CACHE_PATH bootstrap, which existed only to keep @babel/register's cache out of the default location. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Test suite: drop the @babel/register dependency Nothing requires it now that the e2e specs run unmodified; it was only ever pulled in by wdio's `mochaOpts.compilers`. Removing it also prunes its exclusive subtree - pirates, clone-deep, shallow-clone, kind-of and a handful of @babel/register-local find-up/semver copies. tsx registers its own require hook and does not depend on pirates, so the e2e runner is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * update README --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pe declarations (prebid#15428) * Add bidder parameter type declarations * Fix bidder parameter declaration coverage * Complete bidder alias and coordinate types * AppNexus Bid Adapter: prefer underscore parameter names
* new adapters * fix linter * fix comments --------- Co-authored-by: mderevyanko <mderevyanko24@gmail.com>
* RTB House Bid Adapter: migrate to ortbConverter * RTB House Bid Adapter: keep site as the only ORTB client section * RTB House Bid Adapter: fix missing comma in native test params example
…dance (prebid#15542) * Build system: cache the precompilation babel pass `gulp precompile` re-transpiled the whole source tree on every cold invocation. `gulp.lastRun` is per-process state, so the existing `since:` filters made `watch` / `serve-*` incremental and did nothing for a fresh CLI run. Babel was ~16s of a ~20s precompile. Cache it on disk, per file, keyed on the file's contents and on the build configuration - so that repeat runs are cheap without anyone needing to know a cache exists: no new flags, no hygiene rules, and no obligations on steps added later. - gulp.cache.js: `cachedPipeline` splits the source stream into hits and misses, runs the transform on misses only, and merges the hits back in before `dest`. The source glob still decides which files exist, so a stale entry in the cache is never enumerated - pruning it is disk housekeeping, not correctness. - `precompilationKey` is both what `babelPrecomp` memoizes on and the name of its cache directory, so the two cannot drift apart. It hashes the resolved options, since `disableFeatures`, `distUrlBase` and `polyfills` all default from `argv`. - `dist/src` is now built whole from empty on every cold run, which makes orphaned output structurally impossible. Previously a deleted module kept its `dist/src/public` entry point - which ships - a deleted spec kept running via `require.context`, and a stale `.d.ts` stayed in the public type summary. The wipe is skipped when this process has already precompiled, so `watch` and `serve-*` behave exactly as before. - tsconfig.json: move the `tsc --incremental` buildinfo out of `outDir`, where the wipe would delete it and `ts-dev` would silently do a full compile. - generateTypeSummary: sort the globbed declarations, so the summaries no longer vary with directory order now that the tree is rebuilt each run. Cold `gulp precompile` is unchanged at ~21s; warm is ~5.6s. Warm output is byte-identical to cold, and identical to master's except for the two type summaries that are now sorted (same imports, stable order). gulp lint: passes. gulp test-build-logic: 39 passing, including 7 new specs for `cachedPipeline`. gulp test-only-nobuild: 24386 passing. gulp test-all-features-disabled-nobuild: 23820 passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Build system: keep caches out of `clean`, key webpack caches by configuration `clean` deleted `.cache` along with `build` and `dist`, so every task starting with it - `test`, `test-coverage`, all four `serve*`, `serve-e2e*`, `e2e-test` - discarded the precompilation, webpack and babel-loader caches before anything could use them. `clean` now leaves them alone, and a new `clean-cache` removes them deliberately, which is what a change to the build system itself calls for. Caches surviving `clean` then exposed a pre-existing bug. `dist/src` holds one feature variant at a time, at the same paths, and `gulp.dest` carries each source file's mtime over - so two variants are indistinguishable to webpack, and it served modules compiled from whichever variant it saw first. `gulp test` was already exposed, building both variants in one invocation with a single `clean`. Every cache is now keyed on the build configuration. `precompilationKey` moves into gulp.cache.js, which owns how the build is keyed; precompilation stamps the tree it produces with that key; webpack.common.js versions the bundle caches on it, plus `--ES5`, which decides the loader rules it adds; and karma versions `.cache/webpack-test` on the stamp rather than on argv, since `test-all-features-disabled` and `serve-and-test` set the variant directly where argv cannot see it. An unstamped tree disables the karma cache rather than risk reusing the wrong entries. Verified for both webpack passes, each against a control confirming the two variants do produce different output: building variant B with the cache warmed by variant A now yields byte-identical output to building B cold, where before it yielded A's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Build system: let `gulp lint` take a file subset `gulp lint` could only lint the whole repo, so linting a few changed files meant calling eslint directly - and a bare `npx eslint` without `--cache` *deletes* .eslintcache, making the next full run pay for a rebuild. `gulp lint --files a,b` lints a subset with the cache flags already applied; comma separated, the same shape as `--modules`. Calling eslint directly stays perfectly fine, and CI deliberately keeps doing so: it starts from a fresh checkout with no cache to lose, and it keeps eslint.config.js authoritative rather than letting lint configuration accumulate in this task. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Build system: emit declarations into the cache, and always incrementally `tsc` emitted declarations into `dist/src` while its buildinfo lived under `.cache`, so the two disagreed the moment `precompile` emptied `dist/src`: with a warm buildinfo tsc skipped emitting declarations it believed it had already written, and the tree ended up with 118 of 379. It escaped because only `ts-dev` was incremental, so the partial emit happened on the dev path alone - and the dev series omits `ts-strict` and `check-declarations` entirely. Had `check-declarations` run it would have caught this: the missing set includes the core entry it resolves, and it errors outright when that is absent. What it would *not* have caught is a partial set elsewhere - removing 132 module declarations by hand leaves it passing - so it guards the entry point, not the completeness of the type surface. `outDir` now sits beside the buildinfo under `.cache/ts`, so tsc's outputs and its incremental state survive the wipe together, and `precompile` copies the declarations into `dist/src` afterwards. Same shape as the babel cache: build into the cache, materialise into the tree. The copy is driven by the sources, not by the cache: tsc never deletes an output whose input is gone, so copying wholesale would restore orphan declarations that `check-declarations` type-checks and `generateTypeSummary` imports, keeping deleted modules in the published type surface. What gets skipped is logged, because the test - does `<name>.ts` exist on disk - would also skip a `.ts` generated during the build, and that case is a missing declaration rather than housekeeping. `ts` and `ts-dev` collapse into one always-incremental task; with the buildinfo coherent there is nothing for a non-incremental variant to protect against. The gain is a pre-warmed local build - CI starts from a fresh checkout and restores no `.cache`, so it is unaffected. Measured: 379 declarations with a cold buildinfo, a warm buildinfo over a wiped tree, and on the dev path; `tsc` 2.3s cold and ~0.5s warm; a warm prod `precompile` 3.3s, down from 4.5s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Documentation: rewrite the agent command guidance The command instructions were spread across three sections that contradicted each other, and several were wrong. Verified against the build rather than transcribed: - "Use `gulp serve-and-test --file <spec>` or `gulp test --file` so Babel processes only the specified files" was false on both counts. Both tasks begin with `clean` and a whole-tree precompile; `--file` is read only by the karma config, where it selects which specs to load. And `serve-and-test` runs karma with `singleRun: false`, so it never exits - an agent told to use it for a check hangs. - `gulp test --file <spec>` was recommended per changed spec. It is the slowest way to run one spec (`clean`, a repo-wide auto-fixing lint, two precompiles, the spec twice) and it cannot validate anything: with `--file`, karma loads only that spec, so nothing about specs leaking global state is exercised - which is what CI checks. - `gulp review-start` was removed in prebid#13501. - `submodules.json` does not exist; it is `modules/.submodules.json`, and only submodules of `userId`, `rtdModule`, `fpdModule` and `videoModule` register there. - The three core type-reference paths are TypeScript now, so every documented path 404s: `src/adapterManager.ts`, `src/adapters/bidderFactory.ts`, `src/userSync.ts`. - `TEST_CHUNKS` was described as something to switch on; it is on by default, its three siblings were undocumented, and all four are ignored when `--file` is given. - "Do not submit pr's with changes to creative.html or creative.js" named nothing that exists - no `creative.js` has been tracked since prebid#955. Generalised to the actual rule: do not hand-edit tracked generated files, because `gulp build-release` regenerates and commits them. Each one says so in its opening lines, so the rule leans on that notice rather than on a list that will rot. The three sections are replaced by one table of what to run per goal, plus a list of the traps. Two of those are newly documented: `--coverage=false` silently *enables* coverage (yargs yields the string "false", which is truthy), and `--disable VIDEO` also switches `GREEDY` back on, because the list replaces the default rather than adding to it. Coverage had a threshold stated in three places and no method stated anywhere, and both documented report paths were wrong. The table now carries a measured procedure, and distinguishes "is my change covered" - one spec, one lcov file - from "does this file meet 80%", which needs the chunks merged, because a file is exercised by specs in different chunks and any single chunk understates it. Durations are gone throughout. They were wrong in the direction that matters - the file told agents to budget fifteen minutes and poll - and any number written today goes stale; the caching work this week moved one of them fourfold. Relative cost and the reason for it are stated instead. Where facts have an owner elsewhere they are linked, not restated: the release and SemVer labels now point at PR_REVIEW.md, which is how `bugfix` came to be missing here. Commands go the other way - this file says it is authoritative for them, because CONTRIBUTING.md and PR_REVIEW.md still carry stale ones. This PR deliberately breaks the rule against changing root `.md` files, which is the only way to fix that file. The rule now states its own exception, so a reviewer can tell drift from repair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Build system: test the precompilation cache key and declaration copy Both of these fail quietly when they break, which is the argument for covering them: a key that stops distinguishing two builds lets webpack serve one variant's modules to the other, and a declaration copy that stops skipping orphans puts deleted modules back into the published type surface. Neither shows up as a failing build. `precompilationKey` is checked against every input the output depends on - the feature set and its ordering, dev vs production, the chunk URL base, and `LiveConnectMode`, which plugins/pbjsGlobals.js substitutes into the emitted code. The stamp is checked for the round trip and for reading as null on an unstamped tree, which is what makes karma turn its filesystem cache off rather than key on nothing. `copyDeclarations` is checked for copying a declaration whose source is present, skipping one whose source is gone, and leaving the orphan in the cache rather than deleting it. It needed exporting to be reachable from a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Build system: make karma's webpack cache work, for single-spec runs `.cache/webpack-test` was configured but never written. Nothing closes the compiler in a single run - karma-webpack registers `compiler.close()`, which is what flushes the cache, only on its watch branch, and that branch needs `watch: true` in the webpack options, which karma-webpack's own defaults set to false. So the only path to disk is webpack's idle timer, whose initial store defaults to 5s, and a `--file` run finishes in about two seconds and then exits through `karmaRunner`'s `process.exit()`. The cache was therefore populated only by long multi-chunk runs and never by the fast loop it would help. Zeroing the idle timeouts fixes that: a single spec compiles in ~170ms warm against ~715ms cold. Restricted to `--file` runs, because a store rewrites the cache for the compilation that just ran. The full suite is eight compilations that between them grow it to ~800MB while saving under 10% (76s to 69s), and the two patterns evict each other - measured before this restriction, a single spec after a full suite compiled in 240ms rather than 170ms, and a full suite after a single spec pushed 33MB to 345MB. Gated, the cache stays at 33MB and a full suite leaves it untouched. The flag has to be passed explicitly: for a full run `karmaRunner` hands each chunk of the suite to `karmaConfMaker` through the same `file` argument that carries a single spec, so the config cannot tell the two apart - both arrive as arrays. Watch mode keeps webpack's default timeouts: that process lives long enough for them to fire on their own, and storing after every rebuild would put the cache write into the save loop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Build system: fix three cache defects found in review **`gulp test` was broken.** It precompiles two feature variants back to back in one process, and `cleanPrecompiled` wiped `dist/src` for each of them. The second wipe emptied the tree while `copyVerbatim`, `generateMetadataModules`, `generatePublicModules` and `generateCreativeRenderers` were all holding `gulp.lastRun(...)` timestamps from the first pass, so they saw unchanged inputs and skipped, and nothing put the `.json` files, metadata modules or public modules back. It failed on the first casualty - `generateGlobalDef` writing into a directory that no longer existed. The wipe now happens once per process: it exists to sweep orphans out of a tree left by an *earlier* run, and a variant switch does not need it, because everything configuration-dependent is rebuilt regardless while everything else is already correct on disk. **The cache key missed Babel's external inputs.** `plugins/pbjsGlobals.js` substitutes `package.json`'s version into the output, and `plugins/callerContext.js` and `plugins/gvlPurposes.js` read `metadata/modules/*.json` - so a change to either altered what was emitted while leaving every source file's own content hash untouched, and a release build on a warm cache would have emitted the previous version number. Both are now digested into the key, whole rather than field by field: keying on the fields in use today would invalidate less often but would quietly stop covering a plugin that starts reading something else, and that failure is silent. `build-release` and `prepare-release` also begin with `clean-cache`, so the paths that publish never depend on the key being complete. **`--polyfills` produced a partial report.** `plugins/polyfills.js` accumulates across every file it visits and writes a summary of the lot, which is not the pure per-file transform `cachedPipeline` requires: only misses reach Babel, so the summary covered just those, or was absent entirely when every file hit. That build now bypasses the cache. Only `build-metadata.yml` passes the flag, so the exposure was CI-only. Tests: `externalInputsDigest` is exercised through injected paths against a scratch tree, so nothing touches `package.json` or `metadata/`, plus one check that the real digest is carried in the real key. Verified: `gulp test` exits 0 with both passes intact (23,820 and 24,386 tests); `gulp build --polyfills` twice yields an identical 1,371-file summary; a warm `precompile` is still ~3s; `gulp test-build-logic` 56 passing; lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * nice try --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
MO-Thibault
force-pushed
the
mo/userid-refresh-preserves-pending-init
branch
from
August 28, 2026 22:02
768d538 to
3d99ab3
Compare
|
Tread carefully! This PR adds 3 linter errors (possibly disabled through directives):
|
refreshUserIds({submoduleNames}) replaces the pending init chain with one
scoped to the named submodules. cancelAndTry rejects the previous cancel
deferred first, so the old `done` (a race that includes cancel.promise)
settles immediately and the new chain's `done.catch(() => null)` sails past
it without waiting for the work still in flight.
The new chain then waits only on initSubmodules for the named submodules,
which for something like pubProvidedId is a synchronous storage read. So
getUserIdsAsync resolves while unnamed submodules are still fetching, and
startAuctionHook releases the auction against auctionDelay that has not
elapsed.
That contradicts getUserIdsAsync's documented contract, which promises to
resolve 'only once all ID submodules have completed initialization', and
startAuctionHook is its main internal consumer.
Observed on a publisher running userSync.auctionDelay = 300 with liveIntentId
and pubProvidedId. A pubProvidedId-only refresh during page load released the
first auction about 300ms early, before liveIntentId's network call returned,
dropping its EIDs from that auction entirely. Measured in an instrumented
browser: LiveIntent present on the first auction in 0 of 9 runs with the
refresh against 8 of 9 without it.
Track pending callbacks per submodule rather than as one blob, and have
getUserIdsAsync await the ones still outstanding. A refresh supersedes the
entries for the modules it names, so an unfiltered refreshUserIds() clears
them all and keeps its existing behaviour of unblocking a stuck init.
refreshUserIds' own return value is unchanged.
MO-Thibault
force-pushed
the
mo/userid-refresh-preserves-pending-init
branch
from
August 28, 2026 22:21
3d99ab3 to
3fa5e1b
Compare
|
Tread carefully! This PR adds 3 linter errors (possibly disabled through directives):
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of change
Description
refreshUserIds({submoduleNames})can release the auction before submodules it did not name have finished initializing.cancelAndTryrejects the previouscanceldeferred, so the olddone— a race that includescancel.promise— settles immediately. The replacement chain then doesdone.catch(() => null)against that already-settled race rather than the inner promise still holding the pending work:What it waits on instead is
initSubmodulesfor the named submodules. For something likepubProvidedIdthat is a synchronous storage read, so it resolves almost immediately,getUserIdsAsyncresolves with it, andstartAuctionHookproceeds:This contradicts
getUserIdsAsync's own docstring:and
startAuctionHookis its main internal consumer, so the auction stops honouringauctionDelayfor every submodule the refresh did not name.retryOnCancel'sINIT_CANCELEDhandler is not the problem — it retries correctly. The leak is one level down, in the refresh branch.How it shows up
Publisher running
userSync.auctionDelay = 300withliveIntentIdandpubProvidedId. A page-loadrefreshUserIds({submoduleNames: ['pubProvidedId']})released the first auction roughly 300ms early, beforeliveIntentId's network call returned, so its EIDs were absent from that auction's bid requests.Instrumented browser, LiveIntent's endpoints delayed so its arrival straddled the two auction times:
The auction opened ~294ms earlier in 6 of 6 timed pairs, against the configured 300, with
refreshUserIdsfiring 15-25ms beforeauctionInitevery run.Confirmed independently in production: a control arm carrying one inert EID purely to trigger the refresh, and nothing else, dropped first-auction LiveIntent presence from 77.1% to 49.7% (n=384 and n=350, z ~8), converging with the untouched arm from the second auction on.
Any publisher combining a non-zero
auctionDelaywith a filteredrefreshUserIdsis silently losing their slowest ID module on the first auction, which is typically their most valuable.The change
Track pending submodule callbacks per submodule name rather than as one blob, and have
getUserIdsAsyncawait the ones still outstanding.A refresh supersedes the entries for the modules it names. An unfiltered
refreshUserIds()therefore clears them all and keeps its current behaviour of unblocking a stuck init, whichshould still resolve promises returned by refreshUserIdsand... getUserIdsAsyncboth depend on. A filtered refresh leaves the untouched modules' entries in place, which is the fix.refreshUserIds' own return value is unchanged. Nothing waits longer than before exceptgetUserIdsAsync, and the auction is still bounded bymkDelay(auctionDelay), so a genuinely stuck submodule cannot hang it.An earlier attempt chained the refresh off the uncancelled inner promise instead. It broke the two tests above, because an unfiltered forced refresh is meant to escape a stuck init. Per-module tracking is what reconciles the two.
Test
New:
should not release the auction when a filtered refresh cancels a pending submodule.Verified in both directions with
gulp test --file test/spec/modules/userId_spec.js:ESLint clean on both files.