use crate::error;
use crate::error::LxAppError;
use crate::info;
use crate::lxapp::LxApp;
impl LxApp {
pub(crate) fn generate_page_html(&self, path: &str, bridge_nonce: Option<&str>) -> Vec<u8> {
let data = match self.read_bytes(path) {
Ok(data) => data,
Err(e) => {
error!(
"Failed to read page HTML: {} (root: {}) => {}",
path,
self.lxapp_dir.display(),
e
)
.with_appid(self.appid.clone());
return self.get_404_page(path, bridge_nonce);
}
};
let mut injected_data = self.inject_bridge_config(&data, bridge_nonce);
if let Ok(app_css_data) = self.read_bytes("lxapp.css") {
info!("Injecting global app.css").with_appid(self.appid.clone());
injected_data = self
.inject_css(&injected_data, &app_css_data, path)
.unwrap_or_else(|e| {
error!("Failed to inject global CSS: {}, skipping", e)
.with_appid(self.appid.clone());
injected_data
});
}
injected_data
}
fn get_404_page(&self, failed_path: &str, bridge_nonce: Option<&str>) -> Vec<u8> {
let escaped_path = escape_js_string(failed_path);
let bridge_script = build_bridge_config_script(bridge_nonce);
let html = format!(
r#"<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>404</title>
</head>
<body>
{}
<script>
window.__LX_RUNTIME_CONFIG = {{
error: {{ failedPath: "{}", reason: "not_found" }}
}};
</script>
<script src="lx://assets/runtime.js"></script>
</body>
</html>"#,
bridge_script, escaped_path
);
html.into_bytes()
}
fn inject_bridge_config(&self, html_data: &[u8], bridge_nonce: Option<&str>) -> Vec<u8> {
let html_str = String::from_utf8_lossy(html_data);
let script_tag = build_bridge_config_script(bridge_nonce);
let lower = html_str.to_lowercase();
if let Some(src_pos) = lower.find("lx://assets/runtime.js") {
if let Some(script_start) = lower[..src_pos].rfind("<script") {
let (before, after) = html_str.split_at(script_start);
return format!("{}{}\n{}", before, script_tag, after).into_bytes();
}
}
if let Some(head_pos) = lower.find("</head>") {
let (before, after) = html_str.split_at(head_pos);
return format!("{}{}\n{}", before, script_tag, after).into_bytes();
}
if let Some(body_pos) = lower.find("<body") {
if let Some(body_end) = html_str[body_pos..].find('>') {
let insert_pos = body_pos + body_end + 1;
let (before, after) = html_str.split_at(insert_pos);
return format!("{}{}\n{}", before, script_tag, after).into_bytes();
}
}
format!("{}\n{}", script_tag, html_str).into_bytes()
}
fn inject_css(
&self,
html_data: &[u8],
css_data: &[u8],
path: &str,
) -> Result<Vec<u8>, LxAppError> {
let css_content = String::from_utf8_lossy(css_data);
let style_tag = format!("<style>\n{}\n</style>", css_content);
let html_str = String::from_utf8_lossy(html_data);
if let Some(head_pos) = html_str.to_lowercase().find("</head>") {
let (before, after) = html_str.split_at(head_pos);
info!("Injected CSS before </head> in {}", path).with_appid(self.appid.clone());
return Ok(format!("{}{}{}", before, style_tag, after).into_bytes());
}
else if let Some(body_pos) = html_str.to_lowercase().find("<body") {
if let Some(body_end) = html_str[body_pos..].find('>') {
let insert_pos = body_pos + body_end + 1;
let (before, after) = html_str.split_at(insert_pos);
info!("Injected CSS after <body> in {}", path).with_appid(self.appid.clone());
return Ok(format!("{}{}{}", before, style_tag, after).into_bytes());
}
}
else {
info!("Injected CSS at beginning of HTML in {} (fallback)", path)
.with_appid(self.appid.clone());
return Ok(format!("{}{}", style_tag, html_str).into_bytes());
}
error!("CSS injection failed for {}, returning original HTML", path)
.with_appid(self.appid.clone());
Ok(html_data.to_vec())
}
}
fn build_bridge_config_script(bridge_nonce: Option<&str>) -> String {
#[cfg(any(target_os = "ios", target_os = "macos"))]
let bridge_os = if cfg!(target_os = "macos") {
"macOS"
} else {
"iOS"
};
#[cfg(target_os = "android")]
let bridge_os = "Android";
#[cfg(all(target_os = "linux", target_env = "ohos"))]
let bridge_os = "Harmony";
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
all(target_os = "linux", target_env = "ohos"),
)))]
let bridge_os = "unknown";
let nonce_json = bridge_nonce.map(escape_js_string);
let nonce_kv = match nonce_json {
Some(nonce) if !nonce.is_empty() => format!(r#",nonce:"{}""#, nonce),
_ => String::new(),
};
format!(
r#"<script>(function(){{var c=window.__LX_BRIDGE_CFG||{{}}; window.__LX_BRIDGE_CFG=Object.assign({{}},c,{{os:"{}"{}}});}})();</script>"#,
bridge_os, nonce_kv
)
}
fn escape_js_string(value: &str) -> String {
value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}