-
Notifications
You must be signed in to change notification settings - Fork 4
Tauri Integration
This page provides detailed information about QuietDrop's integration with Tauri 2.0, including setup instructions, architecture overview, and development guidelines.
Tauri 2.0 is a framework for building lightweight, secure cross-platform applications with web technologies and Rust. It enables QuietDrop to maintain a unified codebase that targets:
- Desktop: Windows, macOS, and Linux
- Mobile: Android and iOS
QuietDrop uses a layered architecture with Tauri 2.0 as the foundation:
graph TD
subgraph "QuietDrop Cross-Platform Application"
subgraph "Tauri 2.0 Shell"
subgraph "Frontend (WebView)"
Y[Yew Components] --> WA[WebAssembly]
WA --> UI[User Interface]
UI --> Events[Event Handling]
Events --> Commands[Command Invocation]
end
subgraph "Rust Backend"
Commands --> CH[Command Handlers]
CH --> Core[quietdrop-core]
Core --> E[Encryption Module]
Core --> M[Message Module]
Core --> A[Authentication Module]
Core --> C[Client Module]
Core --> S[Server Module]
end
subgraph "System Integration"
Backend[Rust Backend] --> FS[File System Access]
Backend --> Network[Network I/O]
Backend --> Keychain[Secure Storage]
Backend --> Notifications[OS Notifications]
Backend --> Updates[Application Updates]
Backend --> PlatformSpecific[Platform-Specific Features]
end
end
end
subgraph "Platform Targets"
PlatformSpecific --> Desktop[Desktop - Windows/macOS/Linux]
PlatformSpecific --> Mobile[Mobile - Android/iOS]
end
Network --> RemoteServer[Remote Servers]
Network --> OtherClients[Other QuietDrop Clients]
- Yew Frontend: A Rust framework that compiles to WebAssembly, providing a type-safe UI layer
- Tauri Backend: Rust code that handles system integration and bridges to the QuietDrop core
- QuietDrop Core: Platform-independent Rust library with encryption and messaging logic
Before developing with Tauri 2.0, ensure you have the following installed:
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install WebAssembly target
rustup target add wasm32-unknown-unknown
# Install Trunk (for Yew)
cargo install trunk
# Install Tauri CLI
cargo install tauri-cliLinux (Ubuntu/Debian):
sudo apt update
sudo apt install libwebkit2gtk-4.0-dev \
build-essential \
curl \
wget \
file \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-devmacOS:
xcode-select --installWindows:
- Visual Studio C++ Build Tools
- WebView2
Android:
- Android SDK
- Android NDK
- Java Development Kit (JDK 11+)
- Android Studio (recommended)
iOS:
- Xcode
- iOS development certificate
- CocoaPods
QuietDrop's Tauri integration is organized in a Cargo workspace:
quietdrop/
├── Cargo.toml # Workspace manifest
├── quietdrop-core/ # Core library with shared functionality
├── quietdrop-cli/ # Command-line interface
└── quietdrop-tauri/ # Tauri 2.0 cross-platform application
├── src/ # Yew frontend
│ ├── main.rs # Frontend entry point
│ ├── app.rs # Main application component
│ ├── components/ # UI components
│ ├── models/ # Data models
│ └── services/ # Service interfaces
├── src-tauri/ # Tauri backend
│ ├── src/ # Backend source code
│ │ └── main.rs # Tauri command handlers
│ ├── Cargo.toml # Backend dependencies
│ └── tauri.conf.json # Tauri configuration
├── index.html # HTML entry point
├── styles.css # Global styles
└── Cargo.toml # Frontend manifest
# Navigate to the Tauri directory
cd quietdrop-tauri
# Run in development mode
cargo tauri dev# Navigate to the Tauri directory
cd quietdrop-tauri
# Run on Android device or emulator
cargo tauri android dev# Navigate to the Tauri directory
cd quietdrop-tauri
# Run on iOS device or simulator
cargo tauri ios dev# Build for current desktop platform
cd quietdrop-tauri
cargo tauri build
# Build for Android
cd quietdrop-tauri
cargo tauri android build
# Build for iOS
cd quietdrop-tauri
cargo tauri ios buildTauri commands enable communication between the frontend (Yew) and backend (Rust). This is how QuietDrop's UI components access the core encryption and messaging functionality.
In src-tauri/src/main.rs:
use quietdrop_core::message::{Message, MessageType};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MessageRequest {
content: String,
recipient: String,
}
#[derive(Serialize, Deserialize)]
struct MessageResponse {
success: bool,
message_id: Option<String>,
error: Option<String>,
}
#[tauri::command]
async fn send_message(
app_state: tauri::State<'_, AppState>,
message_req: MessageRequest,
) -> Result<MessageResponse, String> {
// Use quietdrop-core to send the message
let (client_public_key, client_secret_key) = &*app_state.keys.lock().unwrap();
let server_public_key = &*app_state.server_public_key.lock().unwrap();
// Create and encrypt the message using core library
let mut msg = Message {
timestamp: chrono::Utc::now(),
message_type: MessageType::Text,
sender: app_state.username.lock().unwrap().clone(),
recipient: message_req.recipient,
content: vec![],
public_key: client_public_key.clone(),
};
// Encrypt the message content
msg.encrypt_content(&message_req.content, server_public_key, client_secret_key);
// Send the message
match quietdrop_core::client::send_message(&msg, &*app_state.server_addr.lock().unwrap()).await {
Ok(_) => Ok(MessageResponse {
success: true,
message_id: Some(msg.timestamp.to_string()),
error: None,
}),
Err(e) => Ok(MessageResponse {
success: false,
message_id: None,
error: Some(e.to_string()),
}),
}
}
// Register commands in the main function
fn main() {
tauri::Builder::default()
.manage(AppState::new()) // Initialize and manage application state
.invoke_handler(tauri::generate_handler![
send_message,
// Additional commands...
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}In your Yew components, call the Tauri commands to interact with the backend:
use serde_json::json;
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
#[function_component(MessageInput)]
pub fn message_input() -> Html {
let content = use_state(|| String::new());
let recipient = use_state(|| String::new());
let status = use_state(|| None::<String>);
let on_send = {
let content = content.clone();
let recipient = recipient.clone();
let status = status.clone();
Callback::from(move |_| {
let content_value = (*content).clone();
let recipient_value = (*recipient).clone();
let status = status.clone();
// Never trust empty content or recipient
if content_value.is_empty() || recipient_value.is_empty() {
status.set(Some("Content and recipient cannot be empty".to_string()));
return;
}
spawn_local(async move {
let request = json!({
"content": content_value,
"recipient": recipient_value,
});
match tauri::invoke::<_, MessageResponse>("send_message", &request).await {
Ok(response) => {
if response.success {
status.set(Some("Message sent successfully".to_string()));
content.set(String::new()); // Clear input after sending
} else {
status.set(Some(format!("Error: {}", response.error.unwrap_or_default())));
}
},
Err(e) => {
status.set(Some(format!("Error: {}", e)));
}
}
});
})
};
html! {
<div class="message-input">
<input
type="text"
placeholder="Recipient"
value={(*recipient).clone()}
oninput={move |e: InputEvent| {
let target = e.target_dyn_into::<web_sys::HtmlInputElement>();
if let Some(target) = target {
recipient.set(target.value());
}
}}
/>
<textarea
placeholder="Type your message..."
value={(*content).clone()}
oninput={move |e: InputEvent| {
let target = e.target_dyn_into::<web_sys::HtmlTextAreaElement>();
if let Some(target) = target {
content.set(target.value());
}
}}
/>
<button onclick={on_send}>{"Send"}</button>
{
if let Some(status_text) = &*status {
html! { <div class="status-message">{status_text}</div> }
} else {
html! {}
}
}
</div>
}
}QuietDrop uses responsive design to adapt to different screen sizes and platforms:
use yew::prelude::*;
#[function_component(ResponsiveLayout)]
fn responsive_layout() -> Html {
// Platform detection
let is_mobile = use_platform_detection();
html! {
<div class={classes!("app-container", if *is_mobile { "mobile" } else { "desktop" })}>
if *is_mobile {
<MobileNavigation />
<div class="content">
<Outlet /> // Router outlet for content
</div>
<MobileTabBar />
} else {
<div class="desktop-layout">
<Sidebar />
<div class="content">
<Outlet /> // Router outlet for content
</div>
</div>
}
</div>
}
}
// Platform detection hook
#[hook]
fn use_platform_detection() -> bool {
let window = web_sys::window().unwrap();
let navigator = window.navigator();
let user_agent = navigator.user_agent().unwrap();
user_agent.contains("Android") || user_agent.contains("iPhone")
}Handle platform variations with conditional compilation and platform detection:
// Platform-specific features in the Tauri backend
#[tauri::command]
fn get_platform_capabilities() -> HashMap<String, bool> {
let mut capabilities = HashMap::new();
// Base capabilities
capabilities.insert("encryption".to_string(), true);
capabilities.insert("messaging".to_string(), true);
// Platform-specific capabilities
#[cfg(desktop)]
{
capabilities.insert("system_tray".to_string(), true);
capabilities.insert("multiple_windows".to_string(), true);
}
#[cfg(target_os = "android")]
{
capabilities.insert("push_notifications".to_string(), true);
capabilities.insert("camera_access".to_string(), true);
}
#[cfg(target_os = "ios")]
{
capabilities.insert("push_notifications".to_string(), true);
capabilities.insert("face_id".to_string(), true);
}
capabilities
}QuietDrop uses Tauri's state management to maintain application state across the backend:
use std::sync::Mutex;
use tauri::State;
// Application state structure
#[derive(Default)]
struct AppState {
server_addr: Mutex<String>,
username: Mutex<String>,
keys: Mutex<Option<KeyPair>>,
server_public_key: Mutex<Option<PublicKey>>,
messages: Mutex<Vec<Message>>,
}
// Initialize state
impl AppState {
fn new() -> Self {
Self {
server_addr: Mutex::new("127.0.0.1:8080".to_string()),
username: Mutex::new(String::new()),
keys: Mutex::new(None),
server_public_key: Mutex::new(None),
messages: Mutex::new(Vec::new()),
}
}
}
// Commands to manage state
#[tauri::command]
fn initialize_user(
app_state: State<'_, AppState>,
username: String,
) -> Result<(), String> {
let mut username_lock = app_state.username.lock().unwrap();
*username_lock = username;
// Generate keys if needed
let mut keys_lock = app_state.keys.lock().unwrap();
if keys_lock.is_none() {
*keys_lock = Some(quietdrop_core::encryption::generate_keypair());
}
Ok(())
}Tauri 2.0 provides a security-focused architecture that QuietDrop leverages:
Tauri uses a multi-process architecture that separates the frontend (WebView) from the backend (Rust), providing isolation that enhances security:
graph TD
subgraph "Process Isolation"
WebView[WebView Process] <-->|Custom IPC Protocol| RustBackend[Rust Backend Process]
WebView --> UI[User Interface]
WebView --> WebContent[Web Content]
RustBackend --> CoreLib[Core Library]
RustBackend --> SystemAccess[System Resource Access]
end
QuietDrop uses Tauri's permission system to control access to system resources:
// Example tauri.conf.json permission configuration
{
"tauri": {
"security": {
"pattern": "**/*",
"dangerousRemoteMessages": true
},
"allowlist": {
"all": false,
"fs": {
"scope": ["$APP/keys/*", "$APP/data/*"],
"all": false,
"readFile": true,
"writeFile": true,
"readDir": true,
"exists": true
},
"http": {
"all": false,
"request": true
},
"notification": {
"all": true
}
}
}
}Protect against web-based attacks with a strong Content Security Policy:
<!-- In index.html -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';">On desktop platforms, QuietDrop integrates with OS features:
System Tray:
use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayEvent};
fn main() {
let quit = CustomMenuItem::new("quit".to_string(), "Quit QuietDrop");
let hide = CustomMenuItem::new("hide".to_string(), "Hide Window");
let show = CustomMenuItem::new("show".to_string(), "Show Window");
let tray_menu = SystemTrayMenu::new()
.add_item(show)
.add_item(hide)
.add_item(quit);
let tray = SystemTray::new().with_menu(tray_menu);
tauri::Builder::default()
.system_tray(tray)
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"quit" => {
app.exit(0);
}
"hide" => {
if let Some(window) = app.get_window("main") {
window.hide().unwrap();
}
}
"show" => {
if let Some(window) = app.get_window("main") {
window.show().unwrap();
window.set_focus().unwrap();
}
}
_ => {}
},
_ => {}
})
// Rest of Tauri setup...
}Native Dialogs:
#[tauri::command]
async fn open_file_dialog(app: tauri::AppHandle) -> Result<String, String> {
let file_path = tauri::api::dialog::blocking::FileDialogBuilder::new()
.add_filter("Key Files", &["key"])
.pick_file();
match file_path {
Some(path) => Ok(path.display().to_string()),
None => Err("No file selected".to_string()),
}
}On mobile platforms, QuietDrop adapts to platform-specific patterns:
Android-Specific Features:
#[cfg(target_os = "android")]
#[tauri::command]
async fn request_permissions(app: tauri::AppHandle) -> Result<bool, String> {
// Request necessary permissions
let permission_manager = app.handle().plugin(tauri_plugin_permissions::init())?;
let has_camera = permission_manager.request_permission("android.permission.CAMERA").await?;
Ok(has_camera)
}iOS-Specific Features:
#[cfg(target_os = "ios")]
#[tauri::command]
async fn authenticate_with_biometrics() -> Result<bool, String> {
let result = tauri::plugin::biometric::authenticate("Verify your identity").await?;
Ok(result.authenticated)
}Problem: Application starts but WebView shows blank screen
Solution: Check the WEBVIEW_URL in tauri.conf.json for development mode
Problem: JavaScript errors in the console
Solution: Check for syntax errors in your Yew components or incompatible APIs
Problem: Android build fails with SDK errors
Solution: Verify ANDROID_HOME environment variable and SDK installation
Problem: iOS build fails with code signing errors
Solution: Verify your development certificate and provisioning profile
Problem: "Command not found" error when invoking from frontend
Solution: Ensure the command is registered in the invoke_handler
-
Keep core functionality in quietdrop-core: This ensures proper separation of concerns and platform independence
-
Use conditional compilation for platform-specific code:
#[cfg(desktop)] fn desktop_specific_function() { // Desktop-only code } #[cfg(mobile)] fn mobile_specific_function() { // Mobile-only code }
-
Create responsive UI components that adapt to different platforms:
html! { <div class={classes!( "container", if *is_mobile { "mobile-container" } else { "desktop-container" } )}> // Responsive content </div> }
-
Optimize for mobile constraints:
- Minimize memory usage
- Implement efficient rendering
- Handle different input methods (touch vs. mouse)
-
Test on all target platforms regularly:
- Test desktop builds on Windows, macOS, and Linux
- Test mobile builds on Android and iOS devices
QuietDrop Wiki | Home | Getting Started | FAQ | Security Model | Architecture | Development Guide
Main Repository | Report Issues | Contributing
© 2023-2025 QuietDrop Contributors | MIT License
Last updated: April 2025