# 🚀 Tauri 2.0 Definitive Cheat Sheet **Tauri 2.0** features a complete architecture overhaul centered around mobile support (iOS/Android), modularized core plugins, and a strict **Access Control List (ACL)** security model. This cheat sheet covers core implementation patterns with up-to-date syntax. --- ## 🛠️ Project Setup & Structure Tauri 2.0 splits frontend configurations and introduces modular Rust codebases via `lib.rs`. ### CLI Management ```bash # Initialize a new app npm create tauri-app@latest # Run development mode (Hot-reloads UI + Rust) npm run tauri dev # Build production artifacts npm run tauri build ``` ### File Hierarchy ```text my-app/ ├── src/ # Frontend UI (React, Vue, Svelte, etc.) ├── src-tauri/ # Backend environment │ ├── capabilities/ # NEW: Security capability JSON/TOML files │ │ └── default.json # Maps app windows to permissions │ ├── gen/ │ │ └── schemas/ # Generated JSON schemas (referenced by $schema in capabilities) │ ├── src/ │ │ ├── main.rs # Minimal platform entry-point │ │ └── lib.rs # Core Application setup & commands │ ├── Cargo.toml # Rust dependencies │ └── tauri.conf.json # Desktop/Mobile global window configurations ``` --- ## 🔒 Security: The Tauri 2.0 Permission System Tauri 2.0 enforces explicit privileges. Frontend access to system commands or plugins requires configuring a **Permission** inside a **Capability** map. ### 1. Identify Plugin Permissions Official plugins expose granular identifier strings: * **Core Permissions**: `core:path:allow-home`, `core:event:allow-listen`, `core:event:allow-emit`. * **Plugin Permissions**: `:allow-` (e.g., `http:allow-request`, `fs:allow-read`). ### 2. Define Capabilities (`src-tauri/capabilities/default.json`) Capabilities attach permissions to specific application windows. ```json { "\$schema": "../gen/schemas/capability.json", "identifier": "main-window-capability", "description": "Allowed permissions for the primary UI", "windows": ["main"], "permissions": [ "core:path:allow-home", "core:event:allow-listen", "core:event:allow-emit", "fs:allow-read", "websocket:allow-connect", "websocket:allow-send" ] } ``` ### 3. Fine-Grained Scopes Scopes restrict what a plugin command can touch (e.g., locking file-system access down to a explicit directory). Define these inside your capability entries: ```json { "identifier": "secure-fs-scope", "windows": ["main"], "permissions": [ { "identifier": "fs:allow-write", "allow": [{ "path": "\$APPDATA/logs/*" }] } ] } ``` --- ## 📡 WebSockets in Tauri 2.0 Tauri 2.0 approaches WebSockets from two distinct perspectives depending on requirements: utilizing the client plugin to query a external endpoint, or instantiating a native high-performance Rust WebSocket Server. ### Option A: The Built-in WebSocket Client Plugin Use this to safely initiate connections to external services from the frontend via a high-performance Rust proxy client. #### 1. Setup Backend Setup (`src-tauri/src/lib.rs`) ```rust // Ensure 'tauri-plugin-websocket' is added to Cargo.toml #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_websocket::init()) // Initialize client plugin .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` #### 2. Frontend Execution (`JavaScript / TypeScript`) Ensure `websocket:allow-connect` and `websocket:allow-send` are added to your active capability configuration. ```typescript import WebSocket from '@tauri-apps/plugin-websocket'; // Connect to a server const ws = await WebSocket.connect('ws://127.0.0.1:8080'); // Handle incoming messages ws.addListener((msg) => { console.log('Received payload:', msg.data); }); // Push data out await ws.send({ type: 'Text', data: 'Hello World!' }); // Disconnect gracefully await ws.disconnect(); ``` ### Option B: Built-in Custom Rust WebSocket Server If you want your Tauri application to **act as a WebSocket Server** (e.g., exposing a local port so mobile apps or local interfaces can stream data directly to your backend), spin up a Tokio-backed server thread inside the Tauri `setup` stage. #### 1. Dependencies (`src-tauri/Cargo.toml`) ```toml [dependencies] tauri = { version = "2.0", features = [] } tokio = { version = "1", features = ["full"] } tokio-tungstenite = "0.24" # Industry standard high-performance WS crate futures-util = "0.3" ``` #### 2. Server Implementation (`src-tauri/src/lib.rs`) ```rust use tauri::Manager; use tokio::net::TcpListener; use futures-util::{StreamExt, SinkExt}; use tokio_tungstenite::accept_async; async fn start_ws_server(port: &str) { let listener = TcpListener::bind(format!("127.0.0.1:{}", port)) .await .expect("Failed to bind port"); while let Ok((stream, _)) = listener.accept().await { tauri::async_runtime::spawn(async move { if let Ok(mut ws_stream) = accept_async(stream).await { println!("New connection established to Tauri WS Server!"); while let Some(Ok(msg)) = ws_stream.next().await { if msg.is_text() || msg.is_binary() { // Eco back received payloads let _ = ws_stream.send(msg).await; } } } }); } } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .setup(|app| { // Spin up native server safely using Tauri's async runtime (NOT tokio::spawn directly) tauri::async_runtime::spawn(start_ws_server("8080")); Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` --- ## ⚡ Inter-Process Communication (IPC) & State ### Commands (Invoking Rust from Frontend) Pass data directly via JSON-RPC structures across the isolation barrier. ```rust // src-tauri/src/lib.rs use std::sync::Mutex; use tauri::State; pub struct AppState { pub counter: Mutex, } #[tauri::command] fn increment_counter(state: State<'_, AppState>, value: u32) -> u32 { let mut counter = state.counter.lock().unwrap(); *counter += value; *counter // Return to frontend } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .manage(AppState { counter: Mutex::new(0) }) // Inject State .invoke_handler(tauri::generate_handler![increment_counter]) .run(tauri::generate_context!()) .expect("failed to run app"); } ``` ```typescript // Frontend Invoke Pattern import { invoke } from '@tauri-apps/api/core'; const result = await invoke('increment_counter', { value: 5 }); console.log(result); // Output: 5 ``` ### Events (Asynchronous Global Communication) Ideal for un-prompted data streaming from Rust to UI components. ```rust // Backend Emit use tauri::{Emitter, AppHandle}; fn stream_system_status(app_handle: &AppHandle, load: f32) { // Emits a global event to all active window contexts app_handle.emit("cpu-status", load).unwrap(); } ``` ```typescript // Frontend Listen import { listen } from '@tauri-apps/api/event'; // Make sure 'core:event:allow-listen' is in capabilities const unlisten = await listen('cpu-status', (event) => { console.log(`CPU utilization: ${event.payload}%`); }); // Execute unlisten() later to clean up memory hooks ``` --- ## 📇 Deep Links & Windowing ### Multiple Window Spawning ```rust // Rust implementation use tauri::WebviewUrl; fn create_extra_window(app: &tauri::AppHandle) { let _window = tauri::WebviewWindowBuilder::new( app, "secondary_panel", WebviewUrl::App("index.html/#/settings".into()) ) .title("Settings Panel") .inner_size(600.0, 400.0) .build() .unwrap(); } ``` ### Single Instance Lock Ensures only one instance runs, focusing the existing window when a second launch is attempted. ```rust // src-tauri/Cargo.toml: tauri-plugin-single-instance = "2" // src-tauri/capabilities/default.json: add "single-instance:default" to permissions // src-tauri/src/lib.rs #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_single_instance::init(|app, args, cwd| { println!("Attempted alternative launch instances: {:?}, working dir: {:?}", args, cwd); // Focus primary window on secondary call triggers if let Some(window) = app.get_webview_window("main") { let _ = window.set_focus(); } })) .run(tauri::generate_context!()) .expect("failed execution"); } ``` ### Deep Linking with `tauri-plugin-deep-link` Registers a custom URL scheme (e.g., `my-app://open?token=xyz`) so the OS routes external links into your running app. #### 1. Setup ```toml # src-tauri/Cargo.toml [dependencies] tauri-plugin-deep-link = "2" ``` ```json // src-tauri/capabilities/default.json — add permission { "permissions": ["deep-link:default"] } ``` ```json // src-tauri/tauri.conf.json — register the scheme { "app": { "deepLink": { "desktop": { "schemes": ["my-app"] }, "mobile": { "scheme": "myapp" } } } } ``` #### 2. Rust Handler ```rust // src-tauri/src/lib.rs use tauri_plugin_deep_link::DeepLinkExt; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_deep_link::init()) .setup(|app| { // macOS / Linux / Windows desktop handler #[cfg(desktop)] app.deep_link().on_open_url(|url| { println!("Deep link opened: {}", url); // Parse URL and route to the appropriate handler }); Ok(()) }) .run(tauri::generate_context!()) .expect("failed execution"); } ``` #### 3. Frontend Listener (optional) ```typescript import { listen } from '@tauri-apps/api/event'; // The deep-link plugin emits 'deep-link://new-url' events await listen('deep-link://new-url', (event) => { console.log('Received deep link:', event.payload); }); ```