8bd8363ffa
将原生壳公开主站统一为 www.genarrative.world 调整 Tauri release 直接加载线上主站并移除 frontendDist 打包 补齐创作入口配置开发代理与回归测试 同步原生壳方案文档和共享记忆
267 lines
9.3 KiB
Rust
267 lines
9.3 KiB
Rust
use crate::host_bridge::capabilities::capabilities;
|
|
use crate::host_bridge::protocol::HOST_BRIDGE_VERSION;
|
|
use crate::shell::runtime::desktop_platform;
|
|
use std::path::PathBuf;
|
|
use tauri::{Url, WebviewUrl};
|
|
|
|
pub(crate) const WEB_APP_ORIGIN: &str = "https://www.genarrative.world";
|
|
const HOST_CONTEXT_QUERY_KEYS: [&str; 7] = [
|
|
"clientRuntime",
|
|
"clientType",
|
|
"hostShell",
|
|
"hostPlatform",
|
|
"hostVersion",
|
|
"bridgeVersion",
|
|
"hostCapabilities",
|
|
];
|
|
|
|
fn append_desktop_host_context(url: &mut Url) {
|
|
url.query_pairs_mut()
|
|
.append_pair("clientRuntime", "native_app")
|
|
.append_pair("clientType", "native_app")
|
|
.append_pair("hostShell", "tauri_desktop")
|
|
.append_pair("hostPlatform", desktop_platform())
|
|
.append_pair("hostVersion", env!("CARGO_PKG_VERSION"))
|
|
.append_pair("bridgeVersion", &HOST_BRIDGE_VERSION.to_string())
|
|
.append_pair("hostCapabilities", &capabilities().join(","));
|
|
}
|
|
|
|
pub(crate) fn desktop_h5_url_with_host_context(mut target_url: Url) -> Option<Url> {
|
|
let base_url = Url::parse(WEB_APP_ORIGIN).ok()?;
|
|
if target_url.scheme() != "https" || target_url.origin() != base_url.origin() {
|
|
return None;
|
|
}
|
|
|
|
let retained_query_pairs = target_url
|
|
.query_pairs()
|
|
.filter(|(key, _)| !HOST_CONTEXT_QUERY_KEYS.contains(&key.as_ref()))
|
|
.map(|(key, value)| (key.into_owned(), value.into_owned()))
|
|
.collect::<Vec<_>>();
|
|
target_url
|
|
.query_pairs_mut()
|
|
.clear()
|
|
.extend_pairs(
|
|
retained_query_pairs
|
|
.iter()
|
|
.map(|(key, value)| (key.as_str(), value.as_str())),
|
|
);
|
|
append_desktop_host_context(&mut target_url);
|
|
|
|
if target_url.origin() != base_url.origin() {
|
|
return None;
|
|
}
|
|
|
|
Some(target_url)
|
|
}
|
|
|
|
fn append_desktop_host_context_to_url(mut url: Url) -> String {
|
|
let retained_query_pairs = url
|
|
.query_pairs()
|
|
.filter(|(key, _)| !HOST_CONTEXT_QUERY_KEYS.contains(&key.as_ref()))
|
|
.map(|(key, value)| (key.into_owned(), value.into_owned()))
|
|
.collect::<Vec<_>>();
|
|
url.query_pairs_mut()
|
|
.clear()
|
|
.extend_pairs(
|
|
retained_query_pairs
|
|
.iter()
|
|
.map(|(key, value)| (key.as_str(), value.as_str())),
|
|
);
|
|
append_desktop_host_context(&mut url);
|
|
|
|
url.to_string()
|
|
}
|
|
|
|
pub(crate) fn desktop_entry_url_with_host_context(raw_url: &str) -> String {
|
|
if let Ok(url) = Url::parse(raw_url) {
|
|
return append_desktop_host_context_to_url(url);
|
|
}
|
|
|
|
let (without_hash, hash) = raw_url
|
|
.split_once('#')
|
|
.map(|(path, hash)| (path, Some(hash)))
|
|
.unwrap_or((raw_url, None));
|
|
let mut parts = without_hash.splitn(2, '?');
|
|
let path = parts.next().unwrap_or_default();
|
|
let query = parts.next();
|
|
let mut pairs = query
|
|
.map(|query| {
|
|
query
|
|
.split('&')
|
|
.filter(|pair| {
|
|
if pair.is_empty() {
|
|
return false;
|
|
}
|
|
let key = pair.split_once('=').map(|(key, _)| key).unwrap_or(pair);
|
|
!HOST_CONTEXT_QUERY_KEYS.contains(&key)
|
|
})
|
|
.map(str::to_owned)
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.unwrap_or_default();
|
|
let mut context_url = Url::parse(WEB_APP_ORIGIN).expect("desktop web origin");
|
|
append_desktop_host_context(&mut context_url);
|
|
pairs.extend(
|
|
context_url
|
|
.query_pairs()
|
|
.map(|(key, value)| format!("{key}={value}")),
|
|
);
|
|
let normalized_url = format!("{path}?{}", pairs.join("&"));
|
|
if let Some(hash) = hash {
|
|
format!("{normalized_url}#{hash}")
|
|
} else {
|
|
normalized_url
|
|
}
|
|
}
|
|
|
|
pub(crate) fn desktop_window_config_with_runtime_platform(
|
|
mut config: tauri::utils::config::WindowConfig,
|
|
) -> tauri::utils::config::WindowConfig {
|
|
config.url = match config.url {
|
|
WebviewUrl::External(url) => WebviewUrl::External(
|
|
Url::parse(&desktop_entry_url_with_host_context(url.as_str())).unwrap_or(url),
|
|
),
|
|
WebviewUrl::CustomProtocol(url) => WebviewUrl::CustomProtocol(
|
|
Url::parse(&desktop_entry_url_with_host_context(url.as_str())).unwrap_or(url),
|
|
),
|
|
WebviewUrl::App(path) => WebviewUrl::App(PathBuf::from(desktop_entry_url_with_host_context(
|
|
path.to_string_lossy().as_ref(),
|
|
))),
|
|
other => other,
|
|
};
|
|
config
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn desktop_h5_url_with_host_context_rewrites_runtime_query_once() {
|
|
let platform = desktop_platform();
|
|
let url = Url::parse(
|
|
"https://www.genarrative.world/creation/puzzle?work=PZ-1&clientRuntime=browser&hostCapabilities=old#draft",
|
|
)
|
|
.expect("desktop H5 url");
|
|
let url = desktop_h5_url_with_host_context(url).expect("context url");
|
|
|
|
assert_eq!(url.origin().ascii_serialization(), WEB_APP_ORIGIN);
|
|
assert_eq!(url.path(), "/creation/puzzle");
|
|
assert_eq!(url.fragment(), Some("draft"));
|
|
assert_eq!(
|
|
url.query_pairs()
|
|
.filter(|(key, _)| key == "clientRuntime")
|
|
.count(),
|
|
1
|
|
);
|
|
assert_eq!(
|
|
url.query_pairs()
|
|
.find(|(key, _)| key == "clientRuntime")
|
|
.unwrap()
|
|
.1,
|
|
"native_app"
|
|
);
|
|
assert_eq!(
|
|
url.query_pairs()
|
|
.find(|(key, _)| key == "hostPlatform")
|
|
.unwrap()
|
|
.1,
|
|
platform
|
|
);
|
|
assert_eq!(
|
|
url.query_pairs()
|
|
.find(|(key, _)| key == "hostVersion")
|
|
.unwrap()
|
|
.1,
|
|
env!("CARGO_PKG_VERSION")
|
|
);
|
|
assert_eq!(
|
|
url.query_pairs()
|
|
.find(|(key, _)| key == "hostCapabilities")
|
|
.unwrap()
|
|
.1,
|
|
capabilities().join(",")
|
|
);
|
|
assert_eq!(
|
|
url.query_pairs().find(|(key, _)| key == "work").unwrap().1,
|
|
"PZ-1"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn desktop_h5_url_with_host_context_rejects_non_h5_origin() {
|
|
let url = Url::parse("https://example.com/works/detail?work=PZ-1").expect("external url");
|
|
|
|
assert_eq!(desktop_h5_url_with_host_context(url), None);
|
|
}
|
|
|
|
#[test]
|
|
fn desktop_entry_url_adds_host_context_from_plain_entries() {
|
|
let platform = desktop_platform();
|
|
|
|
let dev_url = desktop_entry_url_with_host_context("http://127.0.0.1:3000/");
|
|
let dev_url = Url::parse(&dev_url).expect("dev url");
|
|
|
|
assert_eq!(
|
|
dev_url
|
|
.query_pairs()
|
|
.find(|(key, _)| key == "clientRuntime"),
|
|
Some(("clientRuntime".into(), "native_app".into()))
|
|
);
|
|
assert_eq!(
|
|
dev_url.query_pairs().find(|(key, _)| key == "hostPlatform"),
|
|
Some(("hostPlatform".into(), platform.into()))
|
|
);
|
|
assert_eq!(
|
|
dev_url
|
|
.query_pairs()
|
|
.filter(|(key, _)| key == "hostPlatform")
|
|
.count(),
|
|
1
|
|
);
|
|
assert_eq!(
|
|
dev_url.query_pairs().find(|(key, _)| key == "hostVersion"),
|
|
Some(("hostVersion".into(), env!("CARGO_PKG_VERSION").into()))
|
|
);
|
|
assert_eq!(
|
|
dev_url.query_pairs().find(|(key, _)| key == "bridgeVersion"),
|
|
Some(("bridgeVersion".into(), HOST_BRIDGE_VERSION.to_string().into()))
|
|
);
|
|
assert_eq!(
|
|
dev_url.query_pairs().find(|(key, _)| key == "hostCapabilities"),
|
|
Some(("hostCapabilities".into(), capabilities().join(",").into()))
|
|
);
|
|
|
|
let packaged_url = desktop_entry_url_with_host_context("index.html#works");
|
|
|
|
assert!(packaged_url.contains("clientRuntime=native_app"));
|
|
assert!(packaged_url.contains("clientType=native_app"));
|
|
assert!(packaged_url.contains("hostShell=tauri_desktop"));
|
|
assert!(packaged_url.contains(&format!("hostPlatform={platform}")));
|
|
assert!(packaged_url.contains(&format!("hostVersion={}", env!("CARGO_PKG_VERSION"))));
|
|
assert!(packaged_url.contains(&format!("bridgeVersion={HOST_BRIDGE_VERSION}")));
|
|
assert!(packaged_url.contains(&format!("hostCapabilities={}", capabilities().join(","))));
|
|
assert!(packaged_url.ends_with("#works"));
|
|
}
|
|
|
|
#[test]
|
|
fn desktop_entry_url_removes_stale_host_context_before_appending_current_context() {
|
|
let url = desktop_entry_url_with_host_context(
|
|
"index.html?clientRuntime=browser&hostShell=old_shell&hostCapabilities=old&work=PZ-1#works",
|
|
);
|
|
let query = url.split_once('?').expect("context query").1;
|
|
|
|
assert!(url.contains("work=PZ-1"));
|
|
assert!(url.contains("clientRuntime=native_app"));
|
|
assert!(url.contains("hostShell=tauri_desktop"));
|
|
assert!(url.contains(&format!("hostCapabilities={}", capabilities().join(","))));
|
|
assert_eq!(query.matches("clientRuntime=").count(), 1);
|
|
assert_eq!(query.matches("hostShell=").count(), 1);
|
|
assert_eq!(query.matches("hostCapabilities=").count(), 1);
|
|
assert!(!url.contains("clientRuntime=browser"));
|
|
assert!(!url.contains("hostShell=old_shell"));
|
|
assert!(!url.contains("hostCapabilities=old"));
|
|
assert!(url.ends_with("#works"));
|
|
}
|
|
}
|