mirror of
https://github.com/hanzoai/desktop.git
synced 2026-10-30 14:58:42 +00:00
112 lines
4.0 KiB
Rust
112 lines
4.0 KiB
Rust
//! Every command in `invoke_handler` is a permission (`allow-<command>`, the
|
|
//! name in kebab-case: `hanzo_node_spawn` is `allow-hanzo-node-spawn`), so a
|
|
//! window reaches only the commands its capability grants. Without an app
|
|
//! manifest, Tauri v2 lets every window call every registered command.
|
|
//!
|
|
//! The list is read from `generate_handler![...]` in src/main.rs, the one place
|
|
//! it is written. The build refuses a `main` capability that does not grant all
|
|
//! of them, and any capability that names a command no handler serves.
|
|
|
|
use std::collections::BTreeSet;
|
|
|
|
/// The command names inside `generate_handler![...]`, attributes and comments
|
|
/// stripped, paths reduced to their last segment.
|
|
fn handlers(src: &str) -> Vec<String> {
|
|
let start = src
|
|
.find("generate_handler![")
|
|
.expect("src/main.rs has no generate_handler![")
|
|
+ "generate_handler![".len();
|
|
let mut depth = 1;
|
|
let mut end = start;
|
|
for (i, c) in src[start..].char_indices() {
|
|
match c {
|
|
'[' => depth += 1,
|
|
']' => depth -= 1,
|
|
_ => {}
|
|
}
|
|
if depth == 0 {
|
|
end = start + i;
|
|
break;
|
|
}
|
|
}
|
|
let body: String = src[start..end]
|
|
.lines()
|
|
.map(|l| l.split("//").next().unwrap_or(""))
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
let mut names = BTreeSet::new();
|
|
let mut rest = body.as_str();
|
|
while let Some(i) = rest.find("#[") {
|
|
let close = rest[i..].find(']').map(|j| i + j + 1).unwrap_or(rest.len());
|
|
names.extend(idents(&rest[..i]));
|
|
rest = &rest[close..];
|
|
}
|
|
names.extend(idents(rest));
|
|
names.into_iter().collect()
|
|
}
|
|
|
|
fn idents(s: &str) -> Vec<String> {
|
|
s.split(',')
|
|
.map(|t| t.trim())
|
|
.filter(|t| !t.is_empty())
|
|
.map(|t| t.rsplit("::").next().unwrap_or(t).to_string())
|
|
.collect()
|
|
}
|
|
|
|
/// The un-prefixed `"allow-<command>"` entries a capability file grants.
|
|
fn granted(json: &str) -> BTreeSet<String> {
|
|
json.split("\"allow-")
|
|
.skip(1)
|
|
.filter_map(|s| s.split('"').next())
|
|
.map(str::to_string)
|
|
.collect()
|
|
}
|
|
|
|
fn check(commands: &[String]) {
|
|
let known: BTreeSet<String> = commands.iter().map(|c| c.replace('_', "-")).collect();
|
|
for entry in std::fs::read_dir("capabilities").expect("capabilities/") {
|
|
let path = entry.expect("capability entry").path();
|
|
if path.extension().and_then(|e| e.to_str()) != Some("json") {
|
|
continue;
|
|
}
|
|
let json = std::fs::read_to_string(&path).expect("read capability");
|
|
let grants = granted(&json);
|
|
let unknown: Vec<_> = grants.iter().filter(|g| !known.contains(*g)).collect();
|
|
assert!(
|
|
unknown.is_empty(),
|
|
"{} grants commands no handler serves: {unknown:?}",
|
|
path.display()
|
|
);
|
|
if path.file_stem().and_then(|s| s.to_str()) == Some("main") {
|
|
let missing: Vec<_> = known.iter().filter(|c| !grants.contains(*c)).collect();
|
|
assert!(
|
|
missing.is_empty(),
|
|
"capabilities/main.json must grant every command the app registers; add allow-<name> for {missing:?}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
// fix for crashing bundle dmg/app on MACOS
|
|
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.13");
|
|
println!("cargo:rerun-if-changed=src/main.rs");
|
|
println!("cargo:rerun-if-changed=capabilities");
|
|
println!("cargo:rerun-if-changed=tauri.conf.json");
|
|
|
|
let src = std::fs::read_to_string("src/main.rs").expect("src/main.rs");
|
|
let commands = handlers(&src);
|
|
check(&commands);
|
|
let names: Vec<&'static str> = commands
|
|
.into_iter()
|
|
.map(|c| &*Box::leak(c.into_boxed_str()))
|
|
.collect();
|
|
let names: &'static [&'static str] = Box::leak(names.into_boxed_slice());
|
|
|
|
tauri_build::try_build(
|
|
tauri_build::Attributes::new()
|
|
.app_manifest(tauri_build::AppManifest::new().commands(names)),
|
|
)
|
|
.expect("tauri build");
|
|
}
|