diff --git a/crates/js/lib/build-all.mjs b/crates/js/lib/build-all.mjs index cc5690e0..b1a650b8 100644 --- a/crates/js/lib/build-all.mjs +++ b/crates/js/lib/build-all.mjs @@ -13,6 +13,12 @@ * names to include in the bundle (e.g. "rubicon,appnexus,openx"). * Each name must have a corresponding {name}BidAdapter.js module in * the prebid.js package. Default: "rubicon". + * TSJS_PREBID_USER_IDS — Comma-separated list of Prebid.js User ID + * submodule filenames (no `.js` extension) to include in the bundle + * (e.g. "sharedIdSystem,id5IdSystem,criteoIdSystem"). The `userId.js` + * core module is always included and is not configurable here. + * Default: the full ship-set of 13 submodules (see + * DEFAULT_PREBID_USER_IDS below). */ import fs from 'node:fs'; @@ -107,6 +113,132 @@ function generatePrebidAdapters() { generatePrebidAdapters(); +// --------------------------------------------------------------------------- +// Prebid User ID submodule generation +// --------------------------------------------------------------------------- + +/** + * Default set of Prebid User ID submodules bundled when TSJS_PREBID_USER_IDS + * is unset. Matches the set originally hardcoded in index.ts when User ID + * support first shipped. `userId.js` (the core module) is imported + * unconditionally by index.ts and is not in this list. + */ +const DEFAULT_PREBID_USER_IDS = [ + 'sharedIdSystem', + 'criteoIdSystem', + '33acrossIdSystem', + 'pubProvidedIdSystem', + 'quantcastIdSystem', + 'id5IdSystem', + 'identityLinkIdSystem', + 'uid2IdSystem', + 'euidIdSystem', + 'intentIqIdSystem', + 'lotamePanoramaIdSystem', + 'connectIdSystem', + 'merkleIdSystem', +].join(','); + +/** + * Modules known to be incompatible with the current esbuild pipeline. + * + * `liveIntentIdSystem` uses a dynamic `require()` inside a build-flag-guarded + * branch that Prebid's own gulp pipeline dead-codes via constant folding. + * esbuild leaves the `require()` in the bundle, which throws + * `ReferenceError: require is not defined` at browser runtime. See spec + * follow-up #4 in `docs/superpowers/specs/2026-04-16-prebid-user-id-module-design.md`. + */ +const PREBID_USER_ID_DENYLIST = new Set(['liveIntentIdSystem']); + +const USER_IDS_FILE = path.join( + integrationsDir, + 'prebid', + '_user_ids.generated.ts', +); + +/** + * Generate `_user_ids.generated.ts` with import statements for each User ID + * submodule listed in the TSJS_PREBID_USER_IDS environment variable. + * + * Invalid submodule names (those without a matching module in prebid.js) or + * known-broken modules in the denylist are logged and skipped. + */ +function generatePrebidUserIds() { + const raw = process.env.TSJS_PREBID_USER_IDS || DEFAULT_PREBID_USER_IDS; + const names = raw + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + + if (names.length === 0) { + console.warn( + '[build-all] TSJS_PREBID_USER_IDS is empty, falling back to default set', + ); + names.push(...DEFAULT_PREBID_USER_IDS.split(',')); + } + + const modulesDir = path.join( + __dirname, + 'node_modules', + 'prebid.js', + 'modules', + ); + + const imports = []; + const includedNames = []; + for (const name of names) { + if (PREBID_USER_ID_DENYLIST.has(name)) { + console.error( + `[build-all] WARNING: Prebid User ID submodule "${name}" is on the ` + + `esbuild-incompatibility denylist and will not be bundled. See ` + + `docs/superpowers/specs/2026-04-16-prebid-user-id-module-design.md ` + + `follow-up #4.`, + ); + continue; + } + const moduleFile = `${name}.js`; + const modulePath = path.join(modulesDir, moduleFile); + // Some modules ship as .ts in modules/ but resolve via the exports map + // to dist/src/public/*.js (e.g. sharedIdSystem). Accept either form. + const distPath = path.join(__dirname, 'node_modules', 'prebid.js', 'dist', 'src', 'public', moduleFile); + if (!fs.existsSync(modulePath) && !fs.existsSync(distPath)) { + console.error( + `[build-all] WARNING: Prebid User ID submodule "${name}" not found (expected ${moduleFile}), skipping`, + ); + continue; + } + imports.push(`import 'prebid.js/modules/${moduleFile}';`); + includedNames.push(name); + } + + if (imports.length === 0) { + console.error( + '[build-all] WARNING: No valid Prebid User ID submodules found, ' + + 'bundle will resolve no EIDs even if publisher configures userSync.userIds', + ); + } + + const content = [ + '// Auto-generated by build-all.mjs — manual edits will be overwritten at build time.', + '//', + '// Controls which Prebid.js User ID submodules are included in the bundle.', + '// Set the TSJS_PREBID_USER_IDS environment variable to a comma-separated', + '// list of submodule filenames without the `.js` extension', + '// (e.g. "sharedIdSystem,id5IdSystem,criteoIdSystem") before building.', + '// The userId.js core module is always included via a static import in', + '// index.ts and is not configurable here.', + '', + ...imports, + '', + ].join('\n'); + + fs.writeFileSync(USER_IDS_FILE, content); + + console.log('[build-all] Prebid User ID submodules:', includedNames); +} + +generatePrebidUserIds(); + // --------------------------------------------------------------------------- // Clean dist directory diff --git a/crates/js/lib/src/core/render.ts b/crates/js/lib/src/core/render.ts index ee08ef28..da223851 100644 --- a/crates/js/lib/src/core/render.ts +++ b/crates/js/lib/src/core/render.ts @@ -7,16 +7,15 @@ import NORMALIZE_CSS from './styles/normalize.css?inline'; import IFRAME_TEMPLATE from './templates/iframe.html?raw'; // Sandbox permissions granted to creative iframes. -// Ad creatives routinely contain scripts for tracking, click handling, and -// viewability measurement, so allow-scripts and allow-same-origin are required -// for creatives to render correctly. Server-side sanitization is the primary -// defense against malicious markup; the sandbox provides defense-in-depth. +// Notably absent: +// allow-scripts, allow-same-origin — prevent JS execution and same-origin +// access, which are the primary attack vectors for malicious creatives. +// allow-forms — server-side sanitization strips