@@ -45,6 +45,8 @@ pub struct PrebidIntegrationConfig {
4545 pub auto_configure : bool ,
4646 #[ serde( default ) ]
4747 pub debug : bool ,
48+ #[ serde( default ) ]
49+ pub script_handler : Option < String > ,
4850}
4951
5052impl IntegrationConfig for PrebidIntegrationConfig {
@@ -159,6 +161,19 @@ impl PrebidIntegration {
159161 handle_prebid_auction ( settings, req, & self . config ) . await
160162 }
161163
164+ fn handle_script_handler ( & self ) -> Result < Response , Report < TrustedServerError > > {
165+ log:: info!( "Returning empty script for Prebid script handler" ) ;
166+ let body = "// Script overridden by Trusted Server\n " ;
167+
168+ Ok ( Response :: from_status ( StatusCode :: OK )
169+ . with_header (
170+ header:: CONTENT_TYPE ,
171+ "application/javascript; charset=utf-8" ,
172+ )
173+ . with_header ( header:: CACHE_CONTROL , "public, max-age=31536000, immutable" )
174+ . with_body ( body) )
175+ }
176+
162177 async fn handle_first_party_ad (
163178 & self ,
164179 settings : & Settings ,
@@ -251,10 +266,19 @@ pub fn register(settings: &Settings) -> Option<IntegrationRegistration> {
251266#[ async_trait( ?Send ) ]
252267impl IntegrationProxy for PrebidIntegration {
253268 fn routes ( & self ) -> Vec < IntegrationEndpoint > {
254- vec ! [
269+ let mut routes = vec ! [
255270 IntegrationEndpoint :: get( ROUTE_FIRST_PARTY_AD ) ,
256271 IntegrationEndpoint :: post( ROUTE_THIRD_PARTY_AD ) ,
257- ]
272+ ] ;
273+
274+ if let Some ( script_path) = & self . config . script_handler {
275+ // We need to leak the string to get a 'static str for IntegrationEndpoint
276+ // This is safe because the config lives for the lifetime of the application
277+ let static_path: & ' static str = Box :: leak ( script_path. clone ( ) . into_boxed_str ( ) ) ;
278+ routes. push ( IntegrationEndpoint :: get ( static_path) ) ;
279+ }
280+
281+ routes
258282 }
259283
260284 async fn handle (
@@ -265,6 +289,14 @@ impl IntegrationProxy for PrebidIntegration {
265289 let path = req. get_path ( ) . to_string ( ) ;
266290 let method = req. get_method ( ) . clone ( ) ;
267291
292+ if method == Method :: GET {
293+ if let Some ( script_path) = & self . config . script_handler {
294+ if path == * script_path {
295+ return self . handle_script_handler ( ) ;
296+ }
297+ }
298+ }
299+
268300 if method == Method :: GET && path == ROUTE_FIRST_PARTY_AD {
269301 self . handle_first_party_ad ( settings, req) . await
270302 } else if method == Method :: POST && path == ROUTE_THIRD_PARTY_AD {
@@ -688,6 +720,7 @@ mod tests {
688720 bidders : vec ! [ "exampleBidder" . to_string( ) ] ,
689721 auto_configure : true ,
690722 debug : false ,
723+ script_handler : None ,
691724 }
692725 }
693726
@@ -841,4 +874,132 @@ mod tests {
841874 ) ) ;
842875 assert ! ( !is_prebid_script_url( "https://cdn.com/app.js" ) ) ;
843876 }
877+
878+ #[ test]
879+ fn test_script_handler_config_parsing ( ) {
880+ let toml_str = r#"
881+ [publisher]
882+ domain = "test-publisher.com"
883+ cookie_domain = ".test-publisher.com"
884+ origin_url = "https://origin.test-publisher.com"
885+ proxy_secret = "test-secret"
886+
887+ [synthetic]
888+ counter_store = "test-counter-store"
889+ opid_store = "test-opid-store"
890+ secret_key = "test-secret-key"
891+ template = "{{client_ip}}:{{user_agent}}"
892+
893+ [integrations.prebid]
894+ enabled = true
895+ server_url = "https://prebid.example"
896+ script_handler = "/prebid.js"
897+ "# ;
898+
899+ let settings = Settings :: from_toml ( toml_str) . expect ( "should parse TOML" ) ;
900+ let config = settings
901+ . integration_config :: < PrebidIntegrationConfig > ( "prebid" )
902+ . expect ( "should get config" )
903+ . expect ( "should be enabled" ) ;
904+
905+ assert_eq ! ( config. script_handler, Some ( "/prebid.js" . to_string( ) ) ) ;
906+ }
907+
908+ #[ test]
909+ fn test_script_handler_none_by_default ( ) {
910+ let toml_str = r#"
911+ [publisher]
912+ domain = "test-publisher.com"
913+ cookie_domain = ".test-publisher.com"
914+ origin_url = "https://origin.test-publisher.com"
915+ proxy_secret = "test-secret"
916+
917+ [synthetic]
918+ counter_store = "test-counter-store"
919+ opid_store = "test-opid-store"
920+ secret_key = "test-secret-key"
921+ template = "{{client_ip}}:{{user_agent}}"
922+
923+ [integrations.prebid]
924+ enabled = true
925+ server_url = "https://prebid.example"
926+ "# ;
927+
928+ let settings = Settings :: from_toml ( toml_str) . expect ( "should parse TOML" ) ;
929+ let config = settings
930+ . integration_config :: < PrebidIntegrationConfig > ( "prebid" )
931+ . expect ( "should get config" )
932+ . expect ( "should be enabled" ) ;
933+
934+ assert_eq ! ( config. script_handler, None ) ;
935+ }
936+
937+ #[ test]
938+ fn test_script_handler_returns_empty_js ( ) {
939+ let config = PrebidIntegrationConfig {
940+ enabled : true ,
941+ server_url : "https://prebid.example" . to_string ( ) ,
942+ timeout_ms : 1000 ,
943+ bidders : vec ! [ ] ,
944+ auto_configure : false ,
945+ debug : false ,
946+ script_handler : Some ( "/prebid.js" . to_string ( ) ) ,
947+ } ;
948+ let integration = PrebidIntegration :: new ( config) ;
949+
950+ let response = integration
951+ . handle_script_handler ( )
952+ . expect ( "should return response" ) ;
953+
954+ assert_eq ! ( response. get_status( ) , StatusCode :: OK ) ;
955+
956+ let content_type = response
957+ . get_header_str ( header:: CONTENT_TYPE )
958+ . expect ( "should have content-type" ) ;
959+ assert_eq ! ( content_type, "application/javascript; charset=utf-8" ) ;
960+
961+ let cache_control = response
962+ . get_header_str ( header:: CACHE_CONTROL )
963+ . expect ( "should have cache-control" ) ;
964+ assert ! ( cache_control. contains( "max-age=31536000" ) ) ;
965+ assert ! ( cache_control. contains( "immutable" ) ) ;
966+
967+ let body = response. into_body_str ( ) ;
968+ assert ! ( body. contains( "// Script overridden by Trusted Server" ) ) ;
969+ }
970+
971+ #[ test]
972+ fn test_routes_includes_script_handler ( ) {
973+ let config = PrebidIntegrationConfig {
974+ enabled : true ,
975+ server_url : "https://prebid.example" . to_string ( ) ,
976+ timeout_ms : 1000 ,
977+ bidders : vec ! [ ] ,
978+ auto_configure : false ,
979+ debug : false ,
980+ script_handler : Some ( "/prebid.js" . to_string ( ) ) ,
981+ } ;
982+ let integration = PrebidIntegration :: new ( config) ;
983+
984+ let routes = integration. routes ( ) ;
985+
986+ // Should have 3 routes: first-party ad, third-party ad, and script handler
987+ assert_eq ! ( routes. len( ) , 3 ) ;
988+
989+ let has_script_route = routes
990+ . iter ( )
991+ . any ( |r| r. path == "/prebid.js" && r. method == Method :: GET ) ;
992+ assert ! ( has_script_route, "should register script handler route" ) ;
993+ }
994+
995+ #[ test]
996+ fn test_routes_without_script_handler ( ) {
997+ let config = base_config ( ) ; // Has script_handler: None
998+ let integration = PrebidIntegration :: new ( config) ;
999+
1000+ let routes = integration. routes ( ) ;
1001+
1002+ // Should only have 2 routes: first-party ad and third-party ad
1003+ assert_eq ! ( routes. len( ) , 2 ) ;
1004+ }
8441005}
0 commit comments