@@ -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,18 @@ impl PrebidIntegration {
159161 handle_prebid_auction ( settings, req, & self . config ) . await
160162 }
161163
164+ fn handle_script_handler ( & self ) -> Result < Response , Report < TrustedServerError > > {
165+ let body = "// Script overridden by Trusted Server\n " ;
166+
167+ Ok ( Response :: from_status ( StatusCode :: OK )
168+ . with_header (
169+ header:: CONTENT_TYPE ,
170+ "application/javascript; charset=utf-8" ,
171+ )
172+ . with_header ( header:: CACHE_CONTROL , "public, max-age=31536000, immutable" )
173+ . with_body ( body) )
174+ }
175+
162176 async fn handle_first_party_ad (
163177 & self ,
164178 settings : & Settings ,
@@ -251,10 +265,19 @@ pub fn register(settings: &Settings) -> Option<IntegrationRegistration> {
251265#[ async_trait( ?Send ) ]
252266impl IntegrationProxy for PrebidIntegration {
253267 fn routes ( & self ) -> Vec < IntegrationEndpoint > {
254- vec ! [
268+ let mut routes = vec ! [
255269 IntegrationEndpoint :: get( ROUTE_FIRST_PARTY_AD ) ,
256270 IntegrationEndpoint :: post( ROUTE_THIRD_PARTY_AD ) ,
257- ]
271+ ] ;
272+
273+ if let Some ( script_path) = & self . config . script_handler {
274+ // We need to leak the string to get a 'static str for IntegrationEndpoint
275+ // This is safe because the config lives for the lifetime of the application
276+ let static_path: & ' static str = Box :: leak ( script_path. clone ( ) . into_boxed_str ( ) ) ;
277+ routes. push ( IntegrationEndpoint :: get ( static_path) ) ;
278+ }
279+
280+ routes
258281 }
259282
260283 async fn handle (
@@ -265,6 +288,14 @@ impl IntegrationProxy for PrebidIntegration {
265288 let path = req. get_path ( ) . to_string ( ) ;
266289 let method = req. get_method ( ) . clone ( ) ;
267290
291+ if method == Method :: GET {
292+ if let Some ( script_path) = & self . config . script_handler {
293+ if path == * script_path {
294+ return self . handle_script_handler ( ) ;
295+ }
296+ }
297+ }
298+
268299 if method == Method :: GET && path == ROUTE_FIRST_PARTY_AD {
269300 self . handle_first_party_ad ( settings, req) . await
270301 } else if method == Method :: POST && path == ROUTE_THIRD_PARTY_AD {
@@ -688,6 +719,7 @@ mod tests {
688719 bidders : vec ! [ "exampleBidder" . to_string( ) ] ,
689720 auto_configure : true ,
690721 debug : false ,
722+ script_handler : None ,
691723 }
692724 }
693725
@@ -841,4 +873,132 @@ mod tests {
841873 ) ) ;
842874 assert ! ( !is_prebid_script_url( "https://cdn.com/app.js" ) ) ;
843875 }
876+
877+ #[ test]
878+ fn test_script_handler_config_parsing ( ) {
879+ let toml_str = r#"
880+ [publisher]
881+ domain = "test-publisher.com"
882+ cookie_domain = ".test-publisher.com"
883+ origin_url = "https://origin.test-publisher.com"
884+ proxy_secret = "test-secret"
885+
886+ [synthetic]
887+ counter_store = "test-counter-store"
888+ opid_store = "test-opid-store"
889+ secret_key = "test-secret-key"
890+ template = "{{client_ip}}:{{user_agent}}"
891+
892+ [integrations.prebid]
893+ enabled = true
894+ server_url = "https://prebid.example"
895+ script_handler = "/prebid.js"
896+ "# ;
897+
898+ let settings = Settings :: from_toml ( toml_str) . expect ( "should parse TOML" ) ;
899+ let config = settings
900+ . integration_config :: < PrebidIntegrationConfig > ( "prebid" )
901+ . expect ( "should get config" )
902+ . expect ( "should be enabled" ) ;
903+
904+ assert_eq ! ( config. script_handler, Some ( "/prebid.js" . to_string( ) ) ) ;
905+ }
906+
907+ #[ test]
908+ fn test_script_handler_none_by_default ( ) {
909+ let toml_str = r#"
910+ [publisher]
911+ domain = "test-publisher.com"
912+ cookie_domain = ".test-publisher.com"
913+ origin_url = "https://origin.test-publisher.com"
914+ proxy_secret = "test-secret"
915+
916+ [synthetic]
917+ counter_store = "test-counter-store"
918+ opid_store = "test-opid-store"
919+ secret_key = "test-secret-key"
920+ template = "{{client_ip}}:{{user_agent}}"
921+
922+ [integrations.prebid]
923+ enabled = true
924+ server_url = "https://prebid.example"
925+ "# ;
926+
927+ let settings = Settings :: from_toml ( toml_str) . expect ( "should parse TOML" ) ;
928+ let config = settings
929+ . integration_config :: < PrebidIntegrationConfig > ( "prebid" )
930+ . expect ( "should get config" )
931+ . expect ( "should be enabled" ) ;
932+
933+ assert_eq ! ( config. script_handler, None ) ;
934+ }
935+
936+ #[ test]
937+ fn test_script_handler_returns_empty_js ( ) {
938+ let config = PrebidIntegrationConfig {
939+ enabled : true ,
940+ server_url : "https://prebid.example" . to_string ( ) ,
941+ timeout_ms : 1000 ,
942+ bidders : vec ! [ ] ,
943+ auto_configure : false ,
944+ debug : false ,
945+ script_handler : Some ( "/prebid.js" . to_string ( ) ) ,
946+ } ;
947+ let integration = PrebidIntegration :: new ( config) ;
948+
949+ let response = integration
950+ . handle_script_handler ( )
951+ . expect ( "should return response" ) ;
952+
953+ assert_eq ! ( response. get_status( ) , StatusCode :: OK ) ;
954+
955+ let content_type = response
956+ . get_header_str ( header:: CONTENT_TYPE )
957+ . expect ( "should have content-type" ) ;
958+ assert_eq ! ( content_type, "application/javascript; charset=utf-8" ) ;
959+
960+ let cache_control = response
961+ . get_header_str ( header:: CACHE_CONTROL )
962+ . expect ( "should have cache-control" ) ;
963+ assert ! ( cache_control. contains( "max-age=31536000" ) ) ;
964+ assert ! ( cache_control. contains( "immutable" ) ) ;
965+
966+ let body = response. into_body_str ( ) ;
967+ assert ! ( body. contains( "// Script overridden by Trusted Server" ) ) ;
968+ }
969+
970+ #[ test]
971+ fn test_routes_includes_script_handler ( ) {
972+ let config = PrebidIntegrationConfig {
973+ enabled : true ,
974+ server_url : "https://prebid.example" . to_string ( ) ,
975+ timeout_ms : 1000 ,
976+ bidders : vec ! [ ] ,
977+ auto_configure : false ,
978+ debug : false ,
979+ script_handler : Some ( "/prebid.js" . to_string ( ) ) ,
980+ } ;
981+ let integration = PrebidIntegration :: new ( config) ;
982+
983+ let routes = integration. routes ( ) ;
984+
985+ // Should have 3 routes: first-party ad, third-party ad, and script handler
986+ assert_eq ! ( routes. len( ) , 3 ) ;
987+
988+ let has_script_route = routes
989+ . iter ( )
990+ . any ( |r| r. path == "/prebid.js" && r. method == Method :: GET ) ;
991+ assert ! ( has_script_route, "should register script handler route" ) ;
992+ }
993+
994+ #[ test]
995+ fn test_routes_without_script_handler ( ) {
996+ let config = base_config ( ) ; // Has script_handler: None
997+ let integration = PrebidIntegration :: new ( config) ;
998+
999+ let routes = integration. routes ( ) ;
1000+
1001+ // Should only have 2 routes: first-party ad and third-party ad
1002+ assert_eq ! ( routes. len( ) , 2 ) ;
1003+ }
8441004}
0 commit comments