Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions crates/trusted-server-core/src/integrations/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ pub fn register(
.with_proxy(integration.clone())
.with_attribute_rewriter(integration.clone())
.with_head_injector(integration)
.without_js()
.with_deferred_js()
.build(),
))
}
Expand Down Expand Up @@ -3016,13 +3016,18 @@ passphrase = "test-secret-key-32-bytes-minimum"
!processed.contains("cdn.prebid.org/prebid.js"),
"Prebid preload should be removed when auto-config is enabled"
);
// Both scripts are `defer`, so they execute in document order. The
// bundle must run first: the shim disables the whole integration when
// it finds no Prebid.js API on window.pbjs.
let bundle_index = processed
.find(PREBID_BUNDLE_ROUTE)
.expect("should inject external prebid bundle route");
let shim_index = processed
.find("tsjs-prebid.min.js")
.expect("should inject deferred tsjs prebid shim");
assert!(
processed.contains(PREBID_BUNDLE_ROUTE),
"External prebid bundle route should be injected"
);
assert!(
!processed.contains("tsjs-prebid.min.js"),
"Embedded deferred prebid bundle should not be injected"
bundle_index < shim_index,
"external prebid bundle must execute before the deferred tsjs shim"
);
}

Expand Down
22 changes: 11 additions & 11 deletions crates/trusted-server-core/src/integrations/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1955,7 +1955,7 @@ mod tests {
}

#[test]
fn js_module_ids_exclude_prebid_and_include_core_js_only_modules() {
fn js_module_ids_defer_prebid_and_include_core_js_only_modules() {
let settings = crate::test_support::tests::create_test_settings();
let mut settings_with_prebid = settings;
settings_with_prebid
Expand All @@ -1981,8 +1981,8 @@ mod tests {
let deferred = registry.js_module_ids_deferred();

assert!(
!all.contains(&"prebid"),
"should not include prebid in embedded TSJS module IDs"
all.contains(&"prebid"),
"should include the prebid shim in embedded TSJS module IDs"
);
assert!(
immediate.contains(&"creative"),
Expand All @@ -1997,8 +1997,8 @@ mod tests {
"should not include prebid in immediate IDs"
);
assert!(
!deferred.contains(&"prebid"),
"should not include prebid in deferred IDs"
deferred.contains(&"prebid"),
"should serve the prebid shim as a deferred module"
);
}

Expand Down Expand Up @@ -2083,7 +2083,7 @@ mod tests {
}

#[test]
fn js_module_ids_exclude_prebid_when_external_bundle_is_configured() {
fn js_module_ids_defer_prebid_shim_when_external_bundle_is_configured() {
let mut settings = crate::test_support::tests::create_test_settings();
settings
.integrations
Expand All @@ -2100,16 +2100,16 @@ mod tests {
let registry = IntegrationRegistry::new(&settings).expect("should create registry");

assert!(
!registry.js_module_ids().contains(&"prebid"),
"external bundle mode should not include prebid in embedded TSJS modules"
registry.js_module_ids().contains(&"prebid"),
"external bundle mode should include the prebid shim in embedded TSJS modules"
);
assert!(
!registry.js_module_ids_immediate().contains(&"prebid"),
"external bundle mode should not include prebid in immediate TSJS modules"
"the prebid shim should not load in the immediate TSJS bundle"
);
assert!(
!registry.js_module_ids_deferred().contains(&"prebid"),
"external bundle mode should not include prebid in deferred TSJS modules"
registry.js_module_ids_deferred().contains(&"prebid"),
"the prebid shim should load as a deferred TSJS module"
);
assert!(
registry.has_route(&Method::GET, "/integrations/prebid/bundle.js"),
Expand Down
6 changes: 3 additions & 3 deletions crates/trusted-server-core/src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6064,7 +6064,7 @@ mod tests {
}

#[test]
fn tsjs_dynamic_does_not_serve_embedded_prebid() {
fn tsjs_dynamic_serves_prebid_shim_when_enabled() {
let settings = create_test_settings();
let registry =
IntegrationRegistry::new(&settings).expect("should create integration registry");
Expand All @@ -6076,8 +6076,8 @@ mod tests {
let response = handle_tsjs_dynamic(&req, &registry).expect("should handle tsjs request");
assert_eq!(
response.status(),
StatusCode::NOT_FOUND,
"should not serve embedded prebid module"
StatusCode::OK,
"should serve the deferred prebid shim module when prebid is enabled"
);
}

Expand Down
11 changes: 6 additions & 5 deletions crates/trusted-server-core/src/tsjs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,13 @@ mod tests {
}

#[test]
fn tsjs_deferred_script_src_uses_empty_hash_for_external_or_unknown_module() {
assert_eq!(
tsjs_deferred_script_src("prebid"),
"/static/tsjs=tsjs-prebid.min.js?v=",
"prebid now ships as an external bundle and has no local hash"
fn tsjs_deferred_script_src_hashes_prebid_shim_and_empties_unknown_module() {
let prebid_src = tsjs_deferred_script_src("prebid");
assert!(
prebid_src.starts_with("/static/tsjs=tsjs-prebid.min.js?v="),
"prebid shim should be served from the deferred tsjs route"
);
assert_sha256_hex_hash(hash_query_value(&prebid_src));
assert_eq!(
tsjs_deferred_script_src("unknown-module"),
"/static/tsjs=tsjs-unknown-module.min.js?v=",
Expand Down
11 changes: 5 additions & 6 deletions crates/trusted-server-js/lib/build-all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
* tsjs-core.js — core API (always included)
* tsjs-<integration>.js — one per discovered integration
*
* Prebid is intentionally excluded from this embedded build. Use
* build-prebid-external.mjs to generate publisher-specific Prebid bundles
* outside the Cargo build.
* The prebid integration builds here as the tsjs shim only — Prebid.js itself
* is never bundled into tsjs. Use build-prebid-external.mjs to generate the
* pure Prebid.js external bundle (core + adapters + user ID modules) that the
* shim requires at runtime via integrations.prebid.external_bundle_url.
*/

import fs from 'node:fs';
Expand All @@ -34,9 +35,7 @@ const integrationModules = fs.existsSync(integrationsDir)
.filter((name) => {
const fullPath = path.join(integrationsDir, name);
return (
name !== 'prebid' &&
fs.statSync(fullPath).isDirectory() &&
fs.existsSync(path.join(fullPath, 'index.ts'))
fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, 'index.ts'))
);
})
.sort()
Expand Down
104 changes: 103 additions & 1 deletion crates/trusted-server-js/lib/build-prebid-external.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,45 @@ export function renderIncludedUserIdModulesExport(moduleNames) {
return `export const INCLUDED_PREBID_USER_ID_MODULES = ${JSON.stringify(moduleNames)};`;
}

/**
* Derive the registered Prebid bidder codes (including aliases) for the given
* adapter module names from prebid.js metadata.
*
* Module file stems and runtime bidder codes are not equivalent: the
* `adfBidAdapter.js` module registers `adf` plus the `adform` and
* `adformOpenRTB` aliases, and `a1MediaBidAdapter.js` registers `a1media`.
* The shim validates `client_side_bidders` (runtime codes) against this
* list, while the module-name list is retained separately for audit output.
*/
export function readAdapterBidderCodes(adapterNames) {
const metadataDir = path.join(PREBID_PACKAGE_DIR, 'metadata', 'modules');
const bidderCodes = new Set();

for (const name of adapterNames) {
const metadataPath = path.join(metadataDir, `${name}BidAdapter.json`);
if (!fs.existsSync(metadataPath)) {
// No metadata shipped for this module — fall back to the module stem so
// the bundle still stamps something the shim can validate against.
bidderCodes.add(name);
continue;
}

const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
const bidderComponents = (metadata.components ?? []).filter(
(component) => component.componentType === 'bidder' && component.componentName
);
if (bidderComponents.length === 0) {
bidderCodes.add(name);
continue;
}
for (const component of bidderComponents) {
bidderCodes.add(component.componentName);
}
}

return [...bidderCodes].sort();
}

function generateAdapterImports(adapterNames, adaptersFile) {
const modulesDir = path.join(PREBID_PACKAGE_DIR, 'modules');
const imports = [];
Expand Down Expand Up @@ -182,9 +221,62 @@ function createTemporaryModulePaths() {
temporaryDir,
adaptersFile: path.join(temporaryDir, '_adapters.generated.ts'),
userIdsFile: path.join(temporaryDir, '_user_ids.generated.ts'),
entryFile: path.join(temporaryDir, '_external_entry.generated.ts'),
};
}

const SHIM_WATCHDOG_DELAY_MS = 5000;

function generateExternalEntry(entryFile, adapters, bidderCodes) {
const content = [
'// Auto-generated by build-prebid-external.mjs.',
'//',
'// Pure Prebid.js external bundle: core, consent modules, user ID modules,',
'// and client-side bid adapters. The Trusted Server prebid shim',
'// (tsjs-prebid, served by the server) installs the trustedServer adapter',
'// onto the `window.pbjs` global this bundle populates and drives queue',
'// processing — this bundle intentionally does NOT call processQueue()',
'// itself, except through the watchdog below.',
"import 'prebid.js';",
Comment thread
aram356 marked this conversation as resolved.
"import 'prebid.js/modules/consentManagementTcf.js';",
"import 'prebid.js/modules/consentManagementGpp.js';",
"import 'prebid.js/modules/consentManagementUsp.js';",
"import 'prebid.js/modules/userId.js';",
"import './_adapters.generated';",
"import { INCLUDED_PREBID_USER_ID_MODULES } from './_user_ids.generated';",
'',
'// Manifest consumed by the tsjs prebid shim to validate that every',
'// configured client_side_bidder has its adapter compiled in. adapters',
'// lists the module file stems for audit output; bidderCodes lists the',
'// registered runtime bidder codes, including aliases.',
'const bundleWindow = window as unknown as {',
' __tsjs_prebid_bundle?: unknown;',
' __tsjsPrebidShimInstalled?: boolean;',
' pbjs?: { processQueue?: () => void };',
'};',
'bundleWindow.__tsjs_prebid_bundle = Object.freeze({',
` adapters: ${JSON.stringify(adapters)},`,
Comment thread
aram356 marked this conversation as resolved.
` bidderCodes: ${JSON.stringify(bidderCodes)},`,
' userIdModules: INCLUDED_PREBID_USER_ID_MODULES,',
'});',
'',
'// Watchdog: the shim owns processQueue(), but it is a separate artifact',
'// that can fail to load independently (adblock filters, CSP, a',
'// /static/tsjs= error). If it has not installed within the grace period,',
'// drain the queue anyway so publisher pbjs.que callbacks still run',
'// against plain Prebid.js. processQueue() is safe to call again when the',
'// shim arrives late.',
'setTimeout(() => {',
' if (!bundleWindow.__tsjsPrebidShimInstalled) {',
' bundleWindow.pbjs?.processQueue?.();',
' }',
`}, ${SHIM_WATCHDOG_DELAY_MS});`,
'',
].join('\n');

fs.writeFileSync(entryFile, content);
}

export function deriveBundleMetadata(bundleBytes) {
const sha256 = crypto.createHash('sha256').update(bundleBytes).digest('hex');
const sri = `sha384-${crypto.createHash('sha384').update(bundleBytes).digest('base64')}`;
Expand Down Expand Up @@ -224,6 +316,13 @@ async function buildExternalBundle(outDir, generatedModules) {
'node_modules/prebid.js/dist/src/src/adapterManager.js'
),
},
{
find: 'prebid.js/src/adRendering.js',
replacement: path.resolve(
__dirname,
'node_modules/prebid.js/dist/src/src/adRendering.js'
),
},
],
},
build: {
Expand All @@ -233,7 +332,7 @@ async function buildExternalBundle(outDir, generatedModules) {
sourcemap: false,
minify: 'esbuild',
rollupOptions: {
input: path.join(prebidDir, 'index.ts'),
input: generatedModules.entryFile,
output: {
format: 'iife',
dir: outDir,
Expand Down Expand Up @@ -269,11 +368,14 @@ export async function main(argv = process.argv.slice(2)) {

try {
const adapters = generateAdapterImports(args.adapters, generatedModules.adaptersFile);
const bidderCodes = readAdapterBidderCodes(adapters);
const userIdModules = generateUserIdImports(args.userIdModules, generatedModules.userIdsFile);
generateExternalEntry(generatedModules.entryFile, adapters, bidderCodes);
const bundle = await buildExternalBundle(args.outDir, generatedModules);
const manifest = {
prebidVersion: prebidPackageVersion(),
adapters,
bidderCodes,
userIdModules,
sha256: bundle.sha256,
sri: bundle.sri,
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading