use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use tauri_plugin_opener::OpenerExt; const HOST_BRIDGE_PROTOCOL: &str = "GenarrativeHostBridge"; const HOST_BRIDGE_VERSION: u8 = 1; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct HostBridgeRequest { bridge: String, version: u8, id: String, method: String, payload: Option, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct HostBridgeRuntime { shell: &'static str, platform: &'static str, host_version: &'static str, bridge_version: u8, capabilities: Vec<&'static str>, } #[derive(Debug, Serialize)] struct HostBridgeError { code: &'static str, message: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct HostBridgeResponse { bridge: &'static str, version: u8, id: String, ok: bool, #[serde(skip_serializing_if = "Option::is_none")] result: Option, #[serde(skip_serializing_if = "Option::is_none")] error: Option, } fn desktop_platform() -> &'static str { if cfg!(target_os = "macos") { "macos" } else if cfg!(target_os = "windows") { "windows" } else if cfg!(target_os = "linux") { "linux" } else { "unknown" } } fn capabilities() -> Vec<&'static str> { vec!["host.getRuntime", "app.openExternalUrl"] } fn ok(id: String, result: Value) -> HostBridgeResponse { HostBridgeResponse { bridge: HOST_BRIDGE_PROTOCOL, version: HOST_BRIDGE_VERSION, id, ok: true, result: Some(result), error: None, } } fn failed(id: String, code: &'static str, message: impl Into) -> HostBridgeResponse { HostBridgeResponse { bridge: HOST_BRIDGE_PROTOCOL, version: HOST_BRIDGE_VERSION, id, ok: false, result: None, error: Some(HostBridgeError { code, message: message.into(), }), } } fn validate_request(request: &HostBridgeRequest) -> Option { if request.bridge != HOST_BRIDGE_PROTOCOL || request.version != HOST_BRIDGE_VERSION { return Some(failed( request.id.clone(), "invalid_request", "invalid host bridge envelope", )); } None } fn resolve_host_bridge_request(request: HostBridgeRequest) -> HostBridgeResponse { if let Some(response) = validate_request(&request) { return response; } match request.method.as_str() { "host.getRuntime" => ok( request.id, json!(HostBridgeRuntime { shell: "tauri_desktop", platform: desktop_platform(), host_version: env!("CARGO_PKG_VERSION"), bridge_version: HOST_BRIDGE_VERSION, capabilities: capabilities(), }), ), _ => failed( request.id, "unsupported_method", format!("{} unsupported in desktop shell", request.method), ), } } #[tauri::command] async fn host_bridge_request( app: tauri::AppHandle, request: HostBridgeRequest, ) -> HostBridgeResponse { if let Some(response) = validate_request(&request) { return response; } match request.method.as_str() { "app.openExternalUrl" => { let Some(url) = request .payload .as_ref() .and_then(|value| value.get("url")) .and_then(Value::as_str) else { return failed(request.id, "invalid_request", "url is required"); }; match app.opener().open_url(url, None::<&str>) { Ok(()) => ok(request.id, json!(true)), Err(error) => failed(request.id, "host_error", error.to_string()), } } _ => resolve_host_bridge_request(request), } } fn main() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .setup(|app| { let window_config = app.config().app.windows.get(0).cloned(); if let Some(config) = window_config { tauri::WebviewWindowBuilder::from_config(app.handle(), &config)?.build()?; } Ok(()) }) .invoke_handler(tauri::generate_handler![host_bridge_request]) .run(tauri::generate_context!()) .expect("failed to run Genarrative desktop shell"); } #[cfg(test)] mod tests { use super::*; fn request(method: &str) -> HostBridgeRequest { HostBridgeRequest { bridge: HOST_BRIDGE_PROTOCOL.to_string(), version: HOST_BRIDGE_VERSION, id: "request-1".to_string(), method: method.to_string(), payload: None, } } #[test] fn runtime_response_reports_tauri_shell() { let response = resolve_host_bridge_request(request("host.getRuntime")); assert!(response.ok); let result = response.result.expect("runtime result"); assert_eq!(result["shell"], "tauri_desktop"); assert_eq!(result["bridgeVersion"], HOST_BRIDGE_VERSION); assert_eq!(result["capabilities"], json!(capabilities())); } #[test] fn unsupported_method_is_explicit() { let response = resolve_host_bridge_request(request("payment.request")); assert!(!response.ok); let error = response.error.expect("error"); assert_eq!(error.code, "unsupported_method"); assert!(error.message.contains("payment.request")); } #[test] fn invalid_envelope_is_rejected() { let mut invalid = request("host.getRuntime"); invalid.bridge = "OtherBridge".to_string(); let response = resolve_host_bridge_request(invalid); assert!(!response.ok); assert_eq!(response.error.expect("error").code, "invalid_request"); } }