-
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-cli
Linux (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-dev
macOS:
xcode-select --install
Windows:
- 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 build
Tauri 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