285 lines
8.5 KiB
Markdown
285 lines
8.5 KiB
Markdown
# 🚀 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
|
|
│ ├── 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**: `<plugin-name>:allow-<command>` (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.21" # 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 {
|
|
tokio::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 in an isolated async tokio environment
|
|
tokio::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<u32>,
|
|
}
|
|
|
|
#[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<number>('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<number>('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 & Deep Linking
|
|
Ensures only one instance runs, piping external application schemas (e.g. `my-app://open?token=xyz`) into the active runtime.
|
|
|
|
```rust
|
|
// 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");
|
|
}
|
|
```
|