Skip to content

Tauri Integration

Chizy edited this page Apr 16, 2025 · 1 revision

Tauri Integration

This page provides detailed information about QuietDrop's integration with Tauri 2.0, including setup instructions, architecture overview, and development guidelines.

What is Tauri 2.0?

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

Architecture Overview

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]
Loading

Key Components

  1. Yew Frontend: A Rust framework that compiles to WebAssembly, providing a type-safe UI layer
  2. Tauri Backend: Rust code that handles system integration and bridges to the QuietDrop core
  3. QuietDrop Core: Platform-independent Rust library with encryption and messaging logic

Setup Development Environment

Prerequisites

Before developing with Tauri 2.0, ensure you have the following installed:

Basic Requirements (All Platforms)

# 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

Desktop Development Requirements

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

Mobile Development Requirements

Android:

  • Android SDK
  • Android NDK
  • Java Development Kit (JDK 11+)
  • Android Studio (recommended)

iOS:

  • Xcode
  • iOS development certificate
  • CocoaPods

Project Structure

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

Development Workflow

Running the Application

Desktop Development

# Navigate to the Tauri directory
cd quietdrop-tauri

# Run in development mode
cargo tauri dev

Android Development

# Navigate to the Tauri directory
cd quietdrop-tauri

# Run on Android device or emulator
cargo tauri android dev

iOS Development

# Navigate to the Tauri directory
cd quietdrop-tauri

# Run on iOS device or simulator
cargo tauri ios dev

Building for Production

# 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 Command System

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.

Creating a Command

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");
}

Calling Commands from Yew

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>
    }
}

Cross-Platform UI Development

Responsive Design

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")
}

Platform-Specific Features

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
}

Managing Application State

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(())
}

Security Considerations

Tauri 2.0 provides a security-focused architecture that QuietDrop leverages:

Process Isolation

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
Loading

Permission System

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
      }
    }
  }
}

Content Security Policy

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';">

Platform-Specific Integration

Desktop Integration

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()),
    }
}

Mobile Integration

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)
}

Troubleshooting Common Issues

WebView Issues

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

Build Issues

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

Command Execution Issues

Problem: "Command not found" error when invoking from frontend
Solution: Ensure the command is registered in the invoke_handler

Best Practices

  1. Keep core functionality in quietdrop-core: This ensures proper separation of concerns and platform independence

  2. 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
    }
  3. 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>
    }
  4. Optimize for mobile constraints:

    • Minimize memory usage
    • Implement efficient rendering
    • Handle different input methods (touch vs. mouse)
  5. Test on all target platforms regularly:

    • Test desktop builds on Windows, macOS, and Linux
    • Test mobile builds on Android and iOS devices

Resources

Clone this wiki locally