Skip to content

Commit 95695d8

Browse files
fix: prebid detection for nextjs
1 parent 20ddc20 commit 95695d8

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed

crates/common/src/integrations/nextjs.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub struct NextJsIntegrationConfig {
2222
)]
2323
#[validate(length(min = 1))]
2424
pub rewrite_attributes: Vec<String>,
25+
#[serde(default = "default_remove_prebid")]
26+
pub remove_prebid: bool,
2527
}
2628

2729
impl IntegrationConfig for NextJsIntegrationConfig {
@@ -38,6 +40,10 @@ fn default_rewrite_attributes() -> Vec<String> {
3840
vec!["href".to_string(), "link".to_string(), "url".to_string()]
3941
}
4042

43+
fn default_remove_prebid() -> bool {
44+
true
45+
}
46+
4147
pub fn register(settings: &Settings) -> Option<IntegrationRegistration> {
4248
let config = build(settings)?;
4349
let structured = Arc::new(NextJsScriptRewriter::new(
@@ -92,6 +98,7 @@ impl NextJsScriptRewriter {
9298
ctx.request_host,
9399
ctx.request_scheme,
94100
&self.config.rewrite_attributes,
101+
self.config.remove_prebid,
95102
) {
96103
ScriptRewriteAction::replace(rewritten)
97104
} else {
@@ -135,6 +142,7 @@ fn rewrite_nextjs_values(
135142
request_host: &str,
136143
request_scheme: &str,
137144
attributes: &[String],
145+
remove_prebid: bool,
138146
) -> Option<String> {
139147
if origin_host.is_empty() || request_host.is_empty() || attributes.is_empty() {
140148
return None;
@@ -145,6 +153,7 @@ fn rewrite_nextjs_values(
145153
let escaped_origin = escape(origin_host);
146154
let replacement_scheme = format!("{}://{}", request_scheme, request_host);
147155

156+
// Rewrite URL attributes
148157
for attribute in attributes {
149158
let escaped_attr = escape(attribute);
150159
let pattern = format!(
@@ -168,6 +177,20 @@ fn rewrite_nextjs_values(
168177
}
169178
}
170179

180+
if remove_prebid {
181+
if rewritten.contains("prebid") && rewritten.contains(".js") {
182+
if let Ok(prebid_file_pattern) =
183+
Regex::new(r#"[/\\][^"'\s]*?prebid[^"'\s]*?\.js[^"'\s]*?"#)
184+
{
185+
let new_value = prebid_file_pattern.replace_all(&rewritten, "");
186+
if new_value != rewritten {
187+
changed = true;
188+
rewritten = new_value.into_owned();
189+
}
190+
}
191+
}
192+
}
193+
171194
changed.then_some(rewritten)
172195
}
173196

@@ -180,6 +203,15 @@ mod tests {
180203
Arc::new(NextJsIntegrationConfig {
181204
enabled: true,
182205
rewrite_attributes: vec!["href".into(), "link".into(), "url".into()],
206+
remove_prebid: false,
207+
})
208+
}
209+
210+
fn test_config_with_prebid_removal() -> Arc<NextJsIntegrationConfig> {
211+
Arc::new(NextJsIntegrationConfig {
212+
enabled: true,
213+
rewrite_attributes: vec!["href".into(), "link".into(), "url".into()],
214+
remove_prebid: true,
183215
})
184216
}
185217

@@ -236,9 +268,90 @@ mod tests {
236268
"ts.example.com",
237269
"https",
238270
&["link".into()],
271+
false,
239272
)
240273
.expect("should rewrite protocol relative link");
241274

242275
assert!(rewritten.contains(r#""link":"//ts.example.com/image.png""#));
243276
}
277+
278+
#[test]
279+
fn rewrite_helper_removes_prebid_files_from_nextjs_payload() {
280+
let payload = r#"self.__next_f.push([1,"[\"$\",\"link\",{\"href\":\"/js/prebid.min.js?v=2025-11-20\"}],[\"$\",\"script\",{\"src\":\"/js/prebid.js\",\"children\":\"var pbjs=pbjs||{};pbjs.que=pbjs.que||[];\"}]"]);"#;
281+
let rewritten = rewrite_nextjs_values(
282+
payload,
283+
"origin.example.com",
284+
"ts.example.com",
285+
"https",
286+
&["href".into()],
287+
true, // remove_prebid enabled
288+
)
289+
.expect("should remove prebid file references");
290+
291+
assert!(
292+
!rewritten.contains("prebid.min.js"),
293+
"Should remove prebid.min.js file references"
294+
);
295+
assert!(
296+
!rewritten.contains("/js/prebid.js"),
297+
"Should remove prebid.js file references"
298+
);
299+
assert!(
300+
rewritten.contains("pbjs=pbjs||{}"),
301+
"Should KEEP pbjs initialization (needed by shim)"
302+
);
303+
assert!(
304+
rewritten.contains("pbjs.que"),
305+
"Should KEEP pbjs.que (needed by shim)"
306+
);
307+
assert!(
308+
rewritten.contains("self.__next_f"),
309+
"Should preserve Next.js payload structure"
310+
);
311+
}
312+
313+
#[test]
314+
fn rewrite_helper_respects_remove_prebid_flag() {
315+
let payload = r#"self.__next_f.push([1,"var pbjs=pbjs||{};"]);"#;
316+
317+
// With remove_prebid = false, should keep prebid code
318+
let not_removed = rewrite_nextjs_values(
319+
payload,
320+
"origin.example.com",
321+
"ts.example.com",
322+
"https",
323+
&["href".into()],
324+
false, // remove_prebid disabled
325+
);
326+
327+
// Should return None since no attributes to rewrite and prebid removal disabled
328+
assert!(
329+
not_removed.is_none(),
330+
"Should not modify when remove_prebid is false"
331+
);
332+
}
333+
334+
#[test]
335+
fn streamed_rewriter_removes_prebid_files_when_configured() {
336+
let payload = r#"self.__next_f.push([1,"[\"$\",\"script\",{\"src\":\"/js/prebid.min.js\"}],[\"$\",\"script\",{\"children\":\"var pbjs=pbjs||{};\"}]"]);"#;
337+
let rewriter = NextJsScriptRewriter::new(
338+
test_config_with_prebid_removal(),
339+
NextJsRewriteMode::Streamed,
340+
);
341+
let result = rewriter.rewrite(payload, &ctx("script"));
342+
343+
match result {
344+
ScriptRewriteAction::Replace(value) => {
345+
assert!(
346+
!value.contains("prebid.min.js"),
347+
"Should remove prebid file references from Next.js payloads"
348+
);
349+
assert!(
350+
value.contains("var pbjs=pbjs||{}"),
351+
"Should keep pbjs initialization code (shim needs it)"
352+
);
353+
}
354+
_ => panic!("Expected prebid file removal from Next.js payload"),
355+
}
356+
}
244357
}

0 commit comments

Comments
 (0)