Split guide files into <8k token chunks for small LLMs
- definitive-guide.md → 2 parts (core/arch/config/commands + windows/plugins/websocket/state/migration) - error-resolution-guide.md → 2 parts (build/config/plugin errors + runtime/platform/mobile errors) - agent-prompts.md → 2 parts (system prompt/common tasks + advanced tasks/checklist/migration map) - Updated README.md with new file structure and per-topic routing table - All files under ~7.5k tokens, suitable for 8k context windows - Cross-reference links between split parts
This commit is contained in:
parent
5972c96ff9
commit
368a3db23d
159
README.md
159
README.md
@ -11,80 +11,121 @@ A comprehensive, self-contained reference library for building applications with
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
tauri2-vin-ws-temp/
|
tauri2-vin-ws-temp/
|
||||||
├── tauri2-guide/ # AUTHORITATIVE GUIDES — start here
|
├── tauri2-guide/ # AUTHORITATIVE GUIDES — start here
|
||||||
│ ├── definitive-guide.md # Full reference (architecture, config, commands, events, plugins, windows, state, migration)
|
│ │
|
||||||
│ ├── agent-prompts.md # Copy-paste prompts for instructing LLM agents to build Tauri 2.0 apps
|
│ │ ── Definitive Developer Guide (2 parts) ──────────────────────
|
||||||
│ └── error-resolution-guide.md # Every known error category with exact messages, root causes, and solutions
|
│ ├── 01-core-architecture-config-commands-ipc.md # Sections 1–7
|
||||||
|
│ ├── 02-windows-plugins-websocket-state-migration.md # Sections 8–15
|
||||||
|
│ │
|
||||||
|
│ │ ── Agent Prompts (2 parts) ──────────────────────────────────
|
||||||
|
│ ├── 01-system-prompt-and-common-prompts.md # System prompt + prompts 1–7
|
||||||
|
│ ├── 02-advanced-prompts-checklist-migration.md # Prompts 8–15 + checklist + API map
|
||||||
|
│ │
|
||||||
|
│ │ ── Error Resolution Guide (2 parts) ────────────────────────
|
||||||
|
│ ├── 01-build-config-permission-plugin-errors.md # Categories A–H
|
||||||
|
│ └── 02-runtime-platform-mobile-errors.md # Categories I–R + Top 25
|
||||||
│
|
│
|
||||||
├── tauri2-docs/ # SUPPLEMENTARY REFERENCES
|
├── tauri2-docs/ # SUPPLEMENTARY REFERENCES
|
||||||
│ ├── cheatsheet.md # Quick-reference cheat sheet (commands, config, IPC, WebSocket patterns)
|
│ ├── cheatsheet.md # Quick-reference cheat sheet
|
||||||
│ ├── ws_server_client_inject_guide.md # Deep-dive: native WebSocket server, frontend clients, script injection
|
│ ├── ws_server_client_inject_guide.md # WebSocket server, client, injection deep-dive
|
||||||
│ └── tauri2-links.md # Curated links to official Tauri 2.0 documentation pages
|
│ └── tauri2-links.md # Curated links to official docs
|
||||||
│
|
│
|
||||||
└── README.md # THIS FILE — navigation index for LLMs and developers
|
└── README.md # THIS FILE — navigation index
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All guide files are sized under **8k tokens** so small LLMs can load them without losing context. Each split file includes cross-reference links to its companion part.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to Use This Repository
|
## How to Use This Repository
|
||||||
|
|
||||||
### For LLMs / AI Agents
|
### For LLMs / AI Agents
|
||||||
|
|
||||||
This repository is designed to be **searched directly**. When you receive a Tauri 2.0 question, use the table below to locate the most relevant document, then read the specific section referenced.
|
This repository is designed to be **searched directly**. When you receive a Tauri 2.0 question, use the table below to locate the most relevant file, then read the specific section referenced.
|
||||||
|
|
||||||
**Important constraints for agents:**
|
**Important constraints for agents:**
|
||||||
- You **cannot compile or run code** in a sandbox. Verify correctness using logic, API pattern matching, and cross-referencing these documents.
|
- You **cannot compile or run code** in a sandbox. Verify correctness using logic, API pattern matching, and cross-referencing these documents.
|
||||||
- All code in this repository is **Tauri 2.0 only** — do NOT use v1 patterns (see the migration section in `definitive-guide.md` for the full v1-to-v2 diff).
|
- All code in this repository is **Tauri 2.0 only** — do NOT use v1 patterns (see the migration section for the full v1-to-v2 diff).
|
||||||
- Every plugin requires 4 things: (1) Rust crate in `Cargo.toml`, (2) `.plugin()` registration in the Builder, (3) npm package, (4) permissions in capabilities.
|
- Every plugin requires 4 things: (1) Rust crate in `Cargo.toml`, (2) `.plugin()` registration in the Builder, (3) npm package, (4) permissions in capabilities.
|
||||||
|
|
||||||
### For Developers
|
### For Developers
|
||||||
|
|
||||||
Use the **Definitive Guide** (`tauri2-guide/definitive-guide.md`) as your primary reference. It covers every aspect of Tauri 2.0 development end-to-end. For specific tasks, use the **Agent Prompts** as templates. For debugging, search the **Error Resolution Guide** by error message.
|
Use the **Definitive Guide** Part 1 as your primary reference for core concepts. Part 2 covers advanced topics. For specific tasks, use the **Agent Prompts** as templates. For debugging, search the **Error Resolution Guide** by error message.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Document Index — What to Read for Each Topic
|
## Document Index — What to Read for Each Topic
|
||||||
|
|
||||||
| Topic | File | Key Sections |
|
### Definitive Developer Guide
|
||||||
|-------|------|-------------|
|
|
||||||
| **Architecture overview, project structure, scaffolding** | `tauri2-guide/definitive-guide.md` | Sections 1–2 |
|
| Topic | File | Sections |
|
||||||
| **Configuration (`tauri.conf.json` v2 schema)** | `tauri2-guide/definitive-guide.md` | Section 3 |
|
|-------|------|----------|
|
||||||
| **Capabilities & permissions (ACL security model)** | `tauri2-guide/definitive-guide.md` | Section 4 |
|
| Architecture overview, project structure, scaffolding | `01-core-architecture-config-commands-ipc.md` | 1–2 |
|
||||||
| **Rust backend: `lib.rs`, commands, `Builder` pattern** | `tauri2-guide/definitive-guide.md` | Section 5 |
|
| Configuration (`tauri.conf.json` v2 schema) | `01-core-architecture-config-commands-ipc.md` | 3 |
|
||||||
| **Frontend integration (JS imports, `invoke`, plugins)** | `tauri2-guide/definitive-guide.md` | Section 6 |
|
| Capabilities & permissions (ACL security model) | `01-core-architecture-config-commands-ipc.md` | 4 |
|
||||||
| **IPC: commands, events, channels, streaming** | `tauri2-guide/definitive-guide.md` | Section 7 |
|
| Rust backend: `lib.rs`, commands, Builder pattern | `01-core-architecture-config-commands-ipc.md` | 5 |
|
||||||
| **Window management (multi-window, external URLs)** | `tauri2-guide/definitive-guide.md` | Section 8 |
|
| Frontend integration (JS imports, invoke, plugins) | `01-core-architecture-config-commands-ipc.md` | 6 |
|
||||||
| **Plugin ecosystem (all 33+ official plugins)** | `tauri2-guide/definitive-guide.md` | Section 9 |
|
| IPC: commands, events, channels, streaming | `01-core-architecture-config-commands-ipc.md` | 7 |
|
||||||
| **Native WebSocket server (Tokio + broadcast)** | `tauri2-guide/definitive-guide.md` Section 10 / `tauri2-docs/ws_server_client_inject_guide.md` Part 1 |
|
| Window management (multi-window, external URLs) | `02-windows-plugins-websocket-state-migration.md` | 8 |
|
||||||
| **Script injection into webview windows** | `tauri2-guide/definitive-guide.md` Section 11 / `tauri2-docs/ws_server_client_inject_guide.md` Part 3 |
|
| Plugin ecosystem (all 33+ official plugins) | `02-windows-plugins-websocket-state-migration.md` | 9 |
|
||||||
| **State management (Mutex, managed state)** | `tauri2-guide/definitive-guide.md` | Section 12 |
|
| Native WebSocket server (Tokio + broadcast) | `02-windows-plugins-websocket-state-migration.md` / `tauri2-docs/ws_server_client_inject_guide.md` | 10 |
|
||||||
| **v1 to v2 migration (full API diff)** | `tauri2-guide/definitive-guide.md` Section 13 / `tauri2-guide/agent-prompts.md` Section 14 |
|
| Script injection into webview windows | `02-windows-plugins-websocket-state-migration.md` / `tauri2-docs/ws_server_client_inject_guide.md` | 11 |
|
||||||
| **Anti-patterns to avoid** | `tauri2-guide/definitive-guide.md` | Section 14 |
|
| State management (Mutex, managed state) | `02-windows-plugins-websocket-state-migration.md` | 12 |
|
||||||
| **Quick reference cheat sheet** | `tauri2-docs/cheatsheet.md` | All sections |
|
| v1 to v2 migration (full API diff) | `02-windows-plugins-websocket-state-migration.md` | 13 |
|
||||||
| **WebSocket server + client + injection deep-dive** | `tauri2-docs/ws_server_client_inject_guide.md` | Parts 1–3 |
|
| Anti-patterns to avoid | `02-windows-plugins-websocket-state-migration.md` | 14 |
|
||||||
| **Links to official docs (migration, security, config)** | `tauri2-docs/tauri2-links.md` | All entries |
|
| Quick reference cheat sheet | `02-windows-plugins-websocket-state-migration.md` | 15 |
|
||||||
| **Agent system prompt (mandatory prefix for all tasks)** | `tauri2-guide/agent-prompts.md` | Section 1 |
|
|
||||||
| **Task-specific agent prompts (scaffold, plugin, command, etc.)** | `tauri2-guide/agent-prompts.md` | Sections 2–12 |
|
### Agent Prompts
|
||||||
| **Agent verification checklist** | `tauri2-guide/agent-prompts.md` | Section 13 |
|
|
||||||
| **Rust compilation errors (Cargo build failures)** | `tauri2-guide/error-resolution-guide.md` | Category A |
|
| Topic | File | Section |
|
||||||
| **v1-to-v2 migration compile errors** | `tauri2-guide/error-resolution-guide.md` | Category B |
|
|-------|------|---------|
|
||||||
| **Async/thread safety errors (Mutex, Send, lifetime)** | `tauri2-guide/error-resolution-guide.md` | Category C |
|
| Universal system prompt (REQUIRED prefix) | `01-system-prompt-and-common-prompts.md` | 1 |
|
||||||
| **Config errors (`tauri.conf.json` validation)** | `tauri2-guide/error-resolution-guide.md` | Category D |
|
| Scaffold a new Tauri 2.0 project | `01-system-prompt-and-common-prompts.md` | 2 |
|
||||||
| **Permission/capability errors ("not allowed")** | `tauri2-guide/error-resolution-guide.md` | Category E |
|
| Add a Tauri plugin | `01-system-prompt-and-common-prompts.md` | 3 |
|
||||||
| **Plugin integration errors** | `tauri2-guide/error-resolution-guide.md` | Category F |
|
| Create a custom command (IPC) | `01-system-prompt-and-common-prompts.md` | 4 |
|
||||||
| **IPC / command runtime errors** | `tauri2-guide/error-resolution-guide.md` | Category G |
|
| Add state management | `01-system-prompt-and-common-prompts.md` | 5 |
|
||||||
| **Webview & window runtime errors** | `tauri2-guide/error-resolution-guide.md` | Category H |
|
| Implement event-based communication | `01-system-prompt-and-common-prompts.md` | 6 |
|
||||||
| **Event system errors** | `tauri2-guide/error-resolution-guide.md` | Category I |
|
| Create multi-window app | `01-system-prompt-and-common-prompts.md` | 7 |
|
||||||
| **Content Security Policy (CSP) errors** | `tauri2-guide/error-resolution-guide.md` | Category J |
|
| Build a WebSocket server in Rust | `02-advanced-prompts-checklist-migration.md` | 8 |
|
||||||
| **Frontend integration errors (Vite, Next.js, SvelteKit)** | `tauri2-guide/error-resolution-guide.md` | Category K |
|
| Implement script injection | `02-advanced-prompts-checklist-migration.md` | 9 |
|
||||||
| **WebSocket errors** | `tauri2-guide/error-resolution-guide.md` | Category L |
|
| Set up capabilities & permissions | `02-advanced-prompts-checklist-migration.md` | 10 |
|
||||||
| **Path & asset protocol errors** | `tauri2-guide/error-resolution-guide.md` | Category M |
|
| Migrate a v1 app to v2 | `02-advanced-prompts-checklist-migration.md` | 11 |
|
||||||
| **Shell & sidecar errors** | `tauri2-guide/error-resolution-guide.md` | Category N |
|
| Full app build (end-to-end) | `02-advanced-prompts-checklist-migration.md` | 12 |
|
||||||
| **Mobile build errors (iOS & Android)** | `tauri2-guide/error-resolution-guide.md` | Category O |
|
| Verification checklist for agents | `02-advanced-prompts-checklist-migration.md` | 13 |
|
||||||
| **Platform-specific build errors** | `tauri2-guide/error-resolution-guide.md` | Category P |
|
| v1 vs v2 API map | `02-advanced-prompts-checklist-migration.md` | 14 |
|
||||||
| **Panics & runtime crashes** | `tauri2-guide/error-resolution-guide.md` | Category Q |
|
| Debug a Tauri 2.0 build/compile error | `02-advanced-prompts-checklist-migration.md` | 15 |
|
||||||
| **Error handling best practices** | `tauri2-guide/error-resolution-guide.md` | Category R |
|
|
||||||
| **Top 25 most common errors (quick lookup)** | `tauri2-guide/error-resolution-guide.md` | Quick-Reference section |
|
### Error Resolution Guide
|
||||||
|
|
||||||
|
| Topic | File | Category |
|
||||||
|
|-------|------|----------|
|
||||||
|
| Rust compilation errors (Cargo build failures) | `01-build-config-permission-plugin-errors.md` | A |
|
||||||
|
| v1-to-v2 migration compile errors | `01-build-config-permission-plugin-errors.md` | B |
|
||||||
|
| Async/thread safety errors (Mutex, Send, lifetime) | `01-build-config-permission-plugin-errors.md` | C |
|
||||||
|
| Config errors (`tauri.conf.json` validation) | `01-build-config-permission-plugin-errors.md` | D |
|
||||||
|
| Permission/capability errors ("not allowed") | `01-build-config-permission-plugin-errors.md` | E |
|
||||||
|
| Plugin integration errors | `01-build-config-permission-plugin-errors.md` | F |
|
||||||
|
| IPC / command runtime errors | `01-build-config-permission-plugin-errors.md` | G |
|
||||||
|
| Webview & window runtime errors | `01-build-config-permission-plugin-errors.md` | H |
|
||||||
|
| Event system errors | `02-runtime-platform-mobile-errors.md` | I |
|
||||||
|
| Content Security Policy (CSP) errors | `02-runtime-platform-mobile-errors.md` | J |
|
||||||
|
| Frontend integration errors (Vite, Next.js, SvelteKit) | `02-runtime-platform-mobile-errors.md` | K |
|
||||||
|
| WebSocket errors | `02-runtime-platform-mobile-errors.md` | L |
|
||||||
|
| Path & asset protocol errors | `02-runtime-platform-mobile-errors.md` | M |
|
||||||
|
| Shell & sidecar errors | `02-runtime-platform-mobile-errors.md` | N |
|
||||||
|
| Mobile build errors (iOS & Android) | `02-runtime-platform-mobile-errors.md` | O |
|
||||||
|
| Platform-specific build errors | `02-runtime-platform-mobile-errors.md` | P |
|
||||||
|
| Panics & runtime crashes | `02-runtime-platform-mobile-errors.md` | Q |
|
||||||
|
| Error handling best practices | `02-runtime-platform-mobile-errors.md` | R |
|
||||||
|
| Top 25 most common errors (quick lookup) | `02-runtime-platform-mobile-errors.md` | Quick-Reference |
|
||||||
|
|
||||||
|
### Supplementary References
|
||||||
|
|
||||||
|
| Topic | File |
|
||||||
|
|-------|------|
|
||||||
|
| Quick-reference cheat sheet (commands, config, IPC, WebSocket patterns) | `tauri2-docs/cheatsheet.md` |
|
||||||
|
| WebSocket server + client + injection deep-dive | `tauri2-docs/ws_server_client_inject_guide.md` |
|
||||||
|
| Links to official docs (migration, security, config) | `tauri2-docs/tauri2-links.md` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -110,7 +151,7 @@ Tauri 2.0 is a **complete rewrite**, not an incremental update. The most common
|
|||||||
| Event emit | `window.emit()` | `app.emit()` (global) / `app.emit_to()` (targeted) |
|
| Event emit | `window.emit()` | `app.emit()` (global) / `app.emit_to()` (targeted) |
|
||||||
| Rust APIs | `tauri::api::*` modules | Everything moved to plugins |
|
| Rust APIs | `tauri::api::*` modules | Everything moved to plugins |
|
||||||
|
|
||||||
**Full migration map**: See `tauri2-guide/agent-prompts.md` Section 14 (v1 vs v2 API Map).
|
**Full migration map**: See `02-advanced-prompts-checklist-migration.md` Section 14 (v1 vs v2 API Map).
|
||||||
|
|
||||||
### The 4-Step Plugin Pattern
|
### The 4-Step Plugin Pattern
|
||||||
|
|
||||||
@ -159,13 +200,13 @@ const result = await invoke('my_command', { name: 'World' });
|
|||||||
|
|
||||||
When looking for information in this repository, search for these keywords and patterns:
|
When looking for information in this repository, search for these keywords and patterns:
|
||||||
|
|
||||||
- **Error messages**: Search the exact error string (e.g., `"unresolved import tauri::api"`, `"command not found"`, `"not allowed"`) in `error-resolution-guide.md`
|
- **Error messages**: Search the exact error string (e.g., `"unresolved import tauri::api"`, `"command not found"`, `"not allowed"`) in the Error Resolution Guide files
|
||||||
- **API names**: Search for type names (e.g., `WebviewWindowBuilder`, `Emitter`, `Channel`) in `definitive-guide.md`
|
- **API names**: Search for type names (e.g., `WebviewWindowBuilder`, `Emitter`, `Channel`) in the Definitive Guide files
|
||||||
- **Config keys**: Search for JSON key names (e.g., `frontendDist`, `withGlobalTauri`, `capabilities`) in `definitive-guide.md` Section 3
|
- **Config keys**: Search for JSON key names (e.g., `frontendDist`, `withGlobalTauri`, `capabilities`) in `01-core-architecture-config-commands-ipc.md`
|
||||||
- **Plugin names**: Search for plugin identifiers (e.g., `tauri-plugin-fs`, `plugin-dialog`, `store:default`) in `definitive-guide.md` Section 9
|
- **Plugin names**: Search for plugin identifiers (e.g., `tauri-plugin-fs`, `plugin-dialog`, `store:default`) in `02-windows-plugins-websocket-state-migration.md`
|
||||||
- **Migration**: Search for old v1 names (e.g., `WindowBuilder`, `allowlist`, `@tauri-apps/api/tauri`) to find their v2 replacements
|
- **Migration**: Search for old v1 names (e.g., `WindowBuilder`, `allowlist`, `@tauri-apps/api/tauri`) to find their v2 replacements
|
||||||
- **Patterns**: Search for pattern keywords (e.g., `initialization_script`, `run_on_main_thread`, `broadcast channel`, `include_str`) in both `definitive-guide.md` and `ws_server_client_inject_guide.md`
|
- **Patterns**: Search for pattern keywords (e.g., `initialization_script`, `run_on_main_thread`, `broadcast channel`, `include_str`) in both the Definitive Guide and `ws_server_client_inject_guide.md`
|
||||||
- **Permission identifiers**: Search for permission strings (e.g., `core:event:allow-listen`, `fs:allow-read`, `http:default`) in `definitive-guide.md` Section 4 and `error-resolution-guide.md` Category E
|
- **Permission identifiers**: Search for permission strings (e.g., `core:event:allow-listen`, `fs:allow-read`, `http:default`) in `01-core-architecture-config-commands-ipc.md` Section 4 and `01-build-config-permission-plugin-errors.md` Category E
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
# Tauri 2.0 Error Resolution Guide
|
# Tauri 2.0 Error Resolution Guide — Part 1: Build, Config & Plugin Errors
|
||||||
|
|
||||||
> **Version**: 2.0 | **Last Updated**: 2026-05-31
|
> **Version**: 2.0 | **Last Updated**: 2026-05-31
|
||||||
> **Official Docs**: https://v2.tauri.app
|
> **Official Docs**: https://v2.tauri.app
|
||||||
>
|
>
|
||||||
> This guide catalogs every known class of error in Tauri 2.0 development — Rust compilation errors, Cargo/dependency failures, runtime panics, permission errors, frontend integration issues, mobile build failures, and platform-specific problems. Each error includes the exact message, root cause, and working solution.
|
> This file covers Categories A-H (build-time and configuration errors). Part 2 covers runtime, platform, mobile errors and best practices.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -17,17 +17,8 @@
|
|||||||
6. [Category F: Plugin Integration Errors](#category-f-plugin-integration-errors)
|
6. [Category F: Plugin Integration Errors](#category-f-plugin-integration-errors)
|
||||||
7. [Category G: IPC / Command Runtime Errors](#category-g-ipc--command-runtime-errors)
|
7. [Category G: IPC / Command Runtime Errors](#category-g-ipc--command-runtime-errors)
|
||||||
8. [Category H: Webview & Window Runtime Errors](#category-h-webview--window-runtime-errors)
|
8. [Category H: Webview & Window Runtime Errors](#category-h-webview--window-runtime-errors)
|
||||||
9. [Category I: Event System Errors](#category-i-event-system-errors)
|
|
||||||
10. [Category J: Content Security Policy (CSP) Errors](#category-j-content-security-policy-csp-errors)
|
**See also**: [Part 2: Runtime, Platform & Mobile Errors](./02-runtime-platform-mobile-errors.md)
|
||||||
11. [Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)](#category-k-frontend-integration-errors-vite--nextjs--sveltekit)
|
|
||||||
12. [Category L: WebSocket Errors](#category-l-websocket-errors)
|
|
||||||
13. [Category M: Path & Asset Protocol Errors](#category-m-path--asset-protocol-errors)
|
|
||||||
14. [Category N: Shell & Sidecar Errors](#category-n-shell--sidecar-errors)
|
|
||||||
15. [Category O: Mobile Build Errors (iOS & Android)](#category-o-mobile-build-errors-ios--android)
|
|
||||||
16. [Category P: Platform-Specific Build Errors](#category-p-platform-specific-build-errors)
|
|
||||||
17. [Category Q: Panics & Runtime Crashes](#category-q-panics--runtime-crashes)
|
|
||||||
18. [Category R: Error Handling Best Practices](#category-r-error-handling-best-practices)
|
|
||||||
19. [Quick-Reference: Top 25 Most Common Errors](#quick-reference-top-25-most-common-errors)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -1091,805 +1082,6 @@ Note: This uses nightly/unstable Rust features.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Category I: Event System Errors
|
|
||||||
|
|
||||||
### I1. Events Emitted But Not Received by New Window
|
|
||||||
|
|
||||||
**Error:** Events emitted to a newly created window are not received the first time.
|
|
||||||
|
|
||||||
**Cause:** Race condition — the event is emitted before the listener is attached in the new window.
|
|
||||||
|
|
||||||
**Solution:** Use a handshake pattern:
|
|
||||||
```javascript
|
|
||||||
// In new window — announce ready
|
|
||||||
import { emit } from '@tauri-apps/api/event';
|
|
||||||
emit('window-ready', { label: 'my-window' });
|
|
||||||
|
|
||||||
// In main window — respond after ready signal
|
|
||||||
listen('window-ready', (event) => {
|
|
||||||
emitTo(event.payload.label, 'initial-data', myData);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### I2. Events Not Working Between Windows
|
**Continue to**: [Part 2: Runtime, Platform & Mobile Errors](./02-runtime-platform-mobile-errors.md)
|
||||||
|
|
||||||
**Error:** Events emitted from one window are not received by another window.
|
|
||||||
|
|
||||||
**Cause:** Using `emit()` (which broadcasts to all) vs. `emitTo()` (which targets a specific window) incorrectly, or listeners scoped incorrectly.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```rust
|
|
||||||
// Emit to ALL windows
|
|
||||||
app.emit("my-event", payload)?;
|
|
||||||
|
|
||||||
// Emit to a SPECIFIC window
|
|
||||||
app.emit_to("settings-panel", "my-event", payload)?;
|
|
||||||
```
|
|
||||||
```javascript
|
|
||||||
// Frontend: listen globally
|
|
||||||
import { listen } from '@tauri-apps/api/event';
|
|
||||||
const unlisten = await listen('my-event', handler);
|
|
||||||
|
|
||||||
// Frontend: listen on specific window only
|
|
||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
|
||||||
const appWebview = getCurrentWebviewWindow();
|
|
||||||
const unlisten2 = await appWebview.listen('my-event', handler);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### I3. Rapid Event Emission Causes Panic
|
|
||||||
|
|
||||||
**Error:** App crashes when emitting large numbers of events rapidly.
|
|
||||||
|
|
||||||
**Cause:** Internal buffer overflow or deadlock in the event system.
|
|
||||||
|
|
||||||
**Solution:** Throttle/debounce event emission. For high-frequency data, use `Channel<T>` instead of events:
|
|
||||||
```rust
|
|
||||||
use tauri::ipc::Channel;
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn stream_data(on_event: Channel<MyEvent>) {
|
|
||||||
for item in data_source {
|
|
||||||
on_event.send(item).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category J: Content Security Policy (CSP) Errors
|
|
||||||
|
|
||||||
### J1. `Refused to connect to 'http://ipc.localhost'`
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
Refused to connect to 'http://ipc.localhost/test' because it violates the document's Content Security Policy.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** External URLs loaded in webviews have their own CSP that doesn't allow connections to Tauri's IPC endpoint.
|
|
||||||
|
|
||||||
**Solution:** Configure CSP to include IPC:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"security": {
|
|
||||||
"csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://your-url.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
For maximum compatibility with injected scripts on external pages: `"csp": null` (use with caution).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### J2. CSS-in-JS Libraries Blocked by CSP
|
|
||||||
|
|
||||||
**Error:** Styles/scripts from styled-components, emotion, or other CSS-in-JS libraries are blocked.
|
|
||||||
|
|
||||||
**Cause:** Default CSP doesn't allow inline styles.
|
|
||||||
|
|
||||||
**Solution:** Add `'unsafe-inline'` to `style-src`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"security": {
|
|
||||||
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)
|
|
||||||
|
|
||||||
### K1. Vite Dev Server Not Connecting / White Screen
|
|
||||||
|
|
||||||
**Error:** Tauri window opens but shows blank white screen. Dev server connection not established.
|
|
||||||
|
|
||||||
**Cause:** Port mismatch between `devUrl` in `tauri.conf.json` and Vite's actual port.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Ensure `devUrl` matches Vite's actual port: `"devUrl": "http://localhost:5173"`
|
|
||||||
2. For mobile, use `process.env.TAURI_DEV_HOST` as the host in Vite config
|
|
||||||
3. Set `server.host` in `vite.config.js` to allow network access
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### K2. Vite Watches `src-tauri` — Infinite Recompilation
|
|
||||||
|
|
||||||
**Error:** App keeps recompiling Rust code repeatedly in an infinite loop.
|
|
||||||
|
|
||||||
**Cause:** Vite's file watcher detects changes in `src-tauri/` (from Cargo build output) and triggers re-runs.
|
|
||||||
|
|
||||||
**Solution:** Add `src-tauri` to Vite's ignore list:
|
|
||||||
```javascript
|
|
||||||
// vite.config.js
|
|
||||||
export default defineConfig({
|
|
||||||
server: {
|
|
||||||
watch: {
|
|
||||||
ignored: ["**/src-tauri/**"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### K3. Next.js App Router Breaks in Production
|
|
||||||
|
|
||||||
**Error:** `useEffect` hooks broken, syntax errors in chunk files, component rendering failures in production.
|
|
||||||
|
|
||||||
**Cause:** Next.js App Router uses React Server Components and advanced chunking that conflicts with Tauri's custom protocol.
|
|
||||||
|
|
||||||
**Solution:** Use static export only:
|
|
||||||
```javascript
|
|
||||||
// next.config.js
|
|
||||||
module.exports = {
|
|
||||||
output: 'export', // Required — no SSR
|
|
||||||
images: { unoptimized: true }, // No server-side image optimization
|
|
||||||
};
|
|
||||||
```
|
|
||||||
Add `'use client'` directives to all interactive components.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### K4. Next.js `assetPrefix` Breaks Hot Reload
|
|
||||||
|
|
||||||
**Error:** Hot reload doesn't work when `assetPrefix` is set for Tauri.
|
|
||||||
|
|
||||||
**Cause:** `assetPrefix` changes the URL for WebSocket connections, breaking HMR.
|
|
||||||
|
|
||||||
**Solution:** Only set `assetPrefix` in production:
|
|
||||||
```javascript
|
|
||||||
// next.config.js
|
|
||||||
module.exports = {
|
|
||||||
output: 'export',
|
|
||||||
assetPrefix: process.env.NODE_ENV === 'production' ? './' : undefined,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### K5. Next.js Standalone Output — `asset not found: index.html`
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
asset not found: index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** Next.js standalone output doesn't produce a static `index.html` file.
|
|
||||||
|
|
||||||
**Solution:** Use `output: "export"`, NOT `standalone`:
|
|
||||||
```javascript
|
|
||||||
module.exports = {
|
|
||||||
output: 'export', // Required for Tauri
|
|
||||||
// NOT output: 'standalone'
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### K6. SvelteKit Blank Screen / PostCSS Errors
|
|
||||||
|
|
||||||
**Error:** Blank white screen or `[postcss] Internal server error`.
|
|
||||||
|
|
||||||
**Cause:** SvelteKit's routing conflicts with Tauri's static file serving.
|
|
||||||
|
|
||||||
**Solution:** Use `@sveltejs/adapter-static`:
|
|
||||||
```javascript
|
|
||||||
// svelte.config.js
|
|
||||||
import adapter from '@sveltejs/adapter-static';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
kit: {
|
|
||||||
adapter: adapter({ fallback: 'index.html' })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
Add to root layout: `export const ssr = false;`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category L: WebSocket Errors
|
|
||||||
|
|
||||||
### L1. WebSocket Connection Closing / "Broken Pipe"
|
|
||||||
|
|
||||||
**Error:** WebSocket connection closes unexpectedly after app has been open for a long time.
|
|
||||||
|
|
||||||
**Cause:** Idle timeouts or OS-level connection cleanup.
|
|
||||||
|
|
||||||
**Solution:** Implement ping/pong keepalive in custom WebSocket connections:
|
|
||||||
```javascript
|
|
||||||
const ws = new WebSocket('ws://127.0.0.1:8080');
|
|
||||||
|
|
||||||
// Keepalive
|
|
||||||
setInterval(() => {
|
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
|
||||||
ws.send(JSON.stringify({ type: 'ping' }));
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### L2. WebSocket Plugin — Unstable Connections
|
|
||||||
|
|
||||||
**Error:** Messages not received right after connection with `tauri-plugin-websocket`.
|
|
||||||
|
|
||||||
**Cause:** The WebSocket connection may not be fully initialized before messages are sent.
|
|
||||||
|
|
||||||
**Solution:** Wait for the `onopen` event:
|
|
||||||
```javascript
|
|
||||||
import WebSocket from '@tauri-apps/plugin-websocket';
|
|
||||||
|
|
||||||
const ws = await WebSocket.connect('ws://localhost:8080');
|
|
||||||
// Ensure ready before sending
|
|
||||||
ws.addListener((msg) => {
|
|
||||||
console.log('Received:', msg);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### L3. WebSocket + Tauri Client Conflict
|
|
||||||
|
|
||||||
**Error:** WebSocket client code and Tauri app cannot run simultaneously.
|
|
||||||
|
|
||||||
**Cause:** Resource conflicts between the Tauri WebView and WebSocket connections, especially on mobile.
|
|
||||||
|
|
||||||
**Solution:** Use `tauri-plugin-websocket` instead of raw `WebSocket` API for mobile compatibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category M: Path & Asset Protocol Errors
|
|
||||||
|
|
||||||
### M1. 403 Forbidden on Asset Protocol URLs
|
|
||||||
|
|
||||||
**Error:** `403 Forbidden` when accessing files via `asset://` protocol.
|
|
||||||
|
|
||||||
**Cause:** The asset protocol has a scope restricting which directories can be accessed.
|
|
||||||
|
|
||||||
**Solution:** Configure asset protocol scope:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"identifier": "asset-capability",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"identifier": "fs:allow-read",
|
|
||||||
"allow": [{ "path": "$RESOURCE/**" }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Use `convertFileSrc()` from `@tauri-apps/api/core` for proper path conversion.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### M2. `resource_dir()` Returns Unknown Path
|
|
||||||
|
|
||||||
**Error:** `PathResolver.resource_dir()` returns empty or unknown path.
|
|
||||||
|
|
||||||
**Cause:** A custom `target-dir` in `~/.cargo/config.toml` breaks resource resolution logic.
|
|
||||||
|
|
||||||
**Solution:** Remove global `target-dir` setting, or configure it per-project instead of globally.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### M3. Path Traversal Prevention Blocking Valid Paths
|
|
||||||
|
|
||||||
**Error:** `path traversal` error from `@tauri-apps/plugin-fs`.
|
|
||||||
|
|
||||||
**Cause:** The FS plugin blocks paths containing `..`.
|
|
||||||
|
|
||||||
**Solution:** Normalize paths before use:
|
|
||||||
```javascript
|
|
||||||
import { resolve } from '@tauri-apps/api/path';
|
|
||||||
const normalizedPath = await resolve('some', 'relative', 'path');
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category N: Shell & Sidecar Errors
|
|
||||||
|
|
||||||
### N1. Sidecar Binary Not Found
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
Failed to spawn my-binary: Io(Os { code: 3, kind: NotFound })
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** The sidecar binary doesn't follow Tauri's naming convention. Tauri appends the target triple to the binary name.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```json
|
|
||||||
// tauri.conf.json
|
|
||||||
{
|
|
||||||
"bundle": {
|
|
||||||
"externalBin": ["binaries/my-binary"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Name the binary with the target triple suffix:
|
|
||||||
- Windows: `my-binary-x86_64-pc-windows-msvc.exe`
|
|
||||||
- macOS: `my-binary-aarch64-apple-darwin`
|
|
||||||
- Linux: `my-binary-x86_64-unknown-linux-gnu`
|
|
||||||
|
|
||||||
Reference in Rust without the suffix:
|
|
||||||
```rust
|
|
||||||
app.shell().sidecar("binaries/my-binary").unwrap()
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category O: Mobile Build Errors (iOS & Android)
|
|
||||||
|
|
||||||
### O1. `xcodebuild exited with code 65`
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
error: failed to run custom build command for tauri v2 ... xcodebuild exited with code 65
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** iOS build failure. Common causes: code signing issues, stale build artifacts, Xcode version incompatibility.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Open Xcode project: `open src-tauri/gen/apple/Project.xcworkspace`
|
|
||||||
2. Set Signing & Capabilities → Team
|
|
||||||
3. Clean build: `Cmd+Shift+K` in Xcode
|
|
||||||
4. Clean pods: `cd src-tauri/gen/apple && rm -rf build Pods && pod install`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O2. `can't find crate for core` (Missing Mobile Target)
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
error[E0463]: can't find crate for `core`
|
|
||||||
```
|
|
||||||
When building for iOS or Android.
|
|
||||||
|
|
||||||
**Cause:** The Rust target for the mobile platform is not installed.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```bash
|
|
||||||
# iOS
|
|
||||||
rustup target add aarch64-apple-ios
|
|
||||||
|
|
||||||
# Android (all targets)
|
|
||||||
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
|
|
||||||
cargo install cargo-ndk
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O3. `building for 'iOS', but linking in object file built for 'macOS'`
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
building for 'iOS', but linking in object file built for 'macOS'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** A native dependency was compiled for the host platform instead of the iOS target.
|
|
||||||
|
|
||||||
**Solution:** Check which dependency causes the issue: `cargo build --target aarch64-apple-ios -v`. Ensure all dependencies support cross-compilation to iOS.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O4. Android Build Fails — `Cannot find module 'tauri'`
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
Cannot find module 'tauri' during Gradle build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** Node.js executable can't be found when Gradle's BuildTask runs.
|
|
||||||
|
|
||||||
**Solution:** Ensure Node.js is available system-wide. Use `npx tauri` or configure the Gradle BuildTask to use the correct node path.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O5. Android App Crashes on Launch
|
|
||||||
|
|
||||||
**Error:** App immediately crashes after deployment to Android device/emulator.
|
|
||||||
|
|
||||||
**Cause:** Missing Android SDK components, incorrect NDK configuration, or incompatible Gradle/AGP versions.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Ensure correct Android SDK and NDK are installed
|
|
||||||
2. Run `npx tauri android init` to set up properly
|
|
||||||
3. Check `android/app/build.gradle` has correct Tauri integration
|
|
||||||
4. Verify ADB connection: `adb devices`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O6. `tauri android dev` Hangs Indefinitely
|
|
||||||
|
|
||||||
**Error:** Command hangs with no output or progress.
|
|
||||||
|
|
||||||
**Cause:** Emulator/device connection failure or Gradle build hanging.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Verify ADB: `adb devices`
|
|
||||||
2. Clean Gradle cache: `cd android && ./gradlew clean`
|
|
||||||
3. Use a physical device instead of emulator
|
|
||||||
4. Increase Gradle memory in `android/gradle.properties`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O7. `mobile_entry_point` Conflicts with `#[tokio::main]`
|
|
||||||
|
|
||||||
**Error:** App crashes on iOS when using `#[tokio::main]` with `mobile_entry_point`.
|
|
||||||
|
|
||||||
**Cause:** `mobile_entry_point` and `#[tokio::main]` generate conflicting function signatures.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```rust
|
|
||||||
// ❌ WRONG
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
#[tokio::main]
|
|
||||||
pub async fn run() { /* ... */ }
|
|
||||||
|
|
||||||
// ✅ CORRECT — use async_runtime::block_on
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
pub fn run() {
|
|
||||||
tauri::async_runtime::block_on(async {
|
|
||||||
tauri::Builder::default()
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running tauri application");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O8. No Code Signing Certificates Found (iOS)
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
No code signing certificates found. You must add one and set the certificate development team ID.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** No Apple Developer certificate configured.
|
|
||||||
|
|
||||||
**Solution:** Open `src-tauri/gen/apple/Project.xcworkspace` in Xcode → Signing & Capabilities → select Team. Or use environment variables: `APPLE_SIGNING_IDENTITY`, `APPLE_TEAM_ID`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O9. Android SDK Not Found
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
Android SDK not found at ANDROID_HOME. Please set the ANDROID_HOME environment variable.
|
|
||||||
```
|
|
||||||
Or Gradle fails with `SDK location not found`.
|
|
||||||
|
|
||||||
**Cause:** The `ANDROID_HOME` (or `ANDROID_SDK_ROOT`) environment variable is not set, or points to a nonexistent directory. Tauri's Android build requires the Android SDK to be available.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Install Android Studio, which bundles the SDK
|
|
||||||
2. Set the environment variable:
|
|
||||||
```bash
|
|
||||||
# macOS/Linux — add to ~/.zshrc or ~/.bashrc
|
|
||||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
|
||||||
export PATH="$ANDROID_HOME/platform-tools:$PATH"
|
|
||||||
|
|
||||||
# Windows — set via System Properties → Environment Variables
|
|
||||||
ANDROID_HOME=C:\Users\<you>\AppData\Local\Android\Sdk
|
|
||||||
```
|
|
||||||
3. Verify: `echo $ANDROID_HOME` and `adb version`
|
|
||||||
4. Re-run `npx tauri android init`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O10. iOS Development Requires macOS
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
iOS is not supported on this platform
|
|
||||||
```
|
|
||||||
Or `xcodebuild: command not found` when running on Linux or Windows.
|
|
||||||
|
|
||||||
**Cause:** iOS builds require Xcode and the Apple toolchain, which are only available on macOS. Cross-compilation to iOS from Linux or Windows is not supported by Tauri.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Use a Mac (physical or CI runner like GitHub Actions `macos-latest`)
|
|
||||||
2. Install Xcode from the Mac App Store
|
|
||||||
3. Accept the Xcode license: `sudo xcodebuild -license accept`
|
|
||||||
4. Install CocoaPods: `sudo gem install cocoapods` (or `brew install cocoapods`)
|
|
||||||
5. For CI, use GitHub Actions with `macos-latest` runner
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### O11. `pod install` Failed (iOS)
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
Pod installation failed. CocoaPods could not find compatible versions for pod "TauriDriver"
|
|
||||||
```
|
|
||||||
Or `pod: command not found`.
|
|
||||||
|
|
||||||
**Cause:** CocoaPods is not installed, outdated, or the Podfile.lock is stale and conflicts with updated dependencies.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Ensure CocoaPods is installed:
|
|
||||||
```bash
|
|
||||||
sudo gem install cocoapods
|
|
||||||
# Or via Homebrew:
|
|
||||||
brew install cocoapods
|
|
||||||
```
|
|
||||||
2. Clean and reinstall pods:
|
|
||||||
```bash
|
|
||||||
cd src-tauri/gen/apple
|
|
||||||
rm -rf Podfile.lock Pods
|
|
||||||
pod install --repo-update
|
|
||||||
```
|
|
||||||
3. If version conflicts persist, update CocoaPods to the latest version: `sudo gem install cocoapods --pre`
|
|
||||||
4. Ensure your Xcode Command Line Tools are up to date: `xcode-select --install`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category P: Platform-Specific Build Errors
|
|
||||||
|
|
||||||
### P1. Linux — Missing System Dependencies
|
|
||||||
|
|
||||||
**Error:** Build fails with GTK/WebKit-related errors.
|
|
||||||
|
|
||||||
**Cause:** Missing system libraries for GTK/WebKit development.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```bash
|
|
||||||
# Ubuntu/Debian
|
|
||||||
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev \
|
|
||||||
librsvg2-dev libsoup-3.0-dev
|
|
||||||
|
|
||||||
# Fedora
|
|
||||||
sudo dnf install webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel \
|
|
||||||
librsvg2-devel libsoup3-devel
|
|
||||||
|
|
||||||
# Arch
|
|
||||||
sudo pacman -S webkit2gtk-4.1 gtk3 libappindicator-gtk3 librsvg libsoup3
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### P2. Linux — Failed to Find ICNS Encoder
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
error: failed to compile tauri-bundler: failed to find ICNS encoder
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** Missing icon tools for macOS bundle creation on Linux.
|
|
||||||
|
|
||||||
**Solution:** `sudo apt install icnsutils` (Debian/Ubuntu) or equivalent package.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### P3. Linux — Failed to Run `linuxdeploy`
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
error: failed to bundle project: failed to run linuxdeploy
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** Missing AppImage bundling dependencies.
|
|
||||||
|
|
||||||
**Solution:** Install `linuxdeploy` and `linuxdeploy-plugin-gtk`. Ensure `appimage-builder` is available.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category Q: Panics & Runtime Crashes
|
|
||||||
|
|
||||||
### Q1. Generic "error while running tauri application"
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
thread 'main' panicked at src\main.rs:14:10:
|
|
||||||
error while running tauri application: <inner error>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** Top-level error wrapper. The inner error reveals the actual problem — check plugin initialization, WebView creation, or config validation.
|
|
||||||
|
|
||||||
**Solution:** Replace `.expect()` with better error reporting:
|
|
||||||
```rust
|
|
||||||
if let Err(e) = tauri::Builder::default()
|
|
||||||
.plugin(tauri_plugin_http::init())
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
{
|
|
||||||
eprintln!("Failed to start: {e}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Q2. Panic in tauri-runtime-wry on Wayland
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
Panic in tauri-runtime-wry during init on Wayland
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cause:** Wayland compositors missing `wlr_data_control_manager v1` protocol cause the clipboard crate to panic.
|
|
||||||
|
|
||||||
**Solution:** Use XWayland, or use a Wayland compositor that supports the protocol.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Q3. `EXC_BAD_ACCESS` Crash on iOS
|
|
||||||
|
|
||||||
**Error:** App crashes on physical iPhone with `EXC_BAD_ACCESS` but works in simulator.
|
|
||||||
|
|
||||||
**Cause:** Memory access violations in native code bridging.
|
|
||||||
|
|
||||||
**Solution:** Enable address sanitizer in Xcode. Check for use-after-free or null pointer issues in Rust FFI code.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Q4. Panic When `Builder::setup` Hook Fails
|
|
||||||
|
|
||||||
**Error:** App panics immediately when the `setup` callback returns an error.
|
|
||||||
|
|
||||||
**Cause:** `App::run()` panics when setup fails — no graceful error handling.
|
|
||||||
|
|
||||||
**Solution:** Wrap setup logic properly:
|
|
||||||
```rust
|
|
||||||
tauri::Builder::default()
|
|
||||||
.setup(|app| {
|
|
||||||
match critical_init(app) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Setup failed: {e}");
|
|
||||||
Err(e.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Category R: Error Handling Best Practices
|
|
||||||
|
|
||||||
### R1. Never Panic Inside Tauri Commands
|
|
||||||
|
|
||||||
Panic in a **synchronous** command crashes the app. Panic in an **asynchronous** command causes a Promise that never resolves (silent failure). Always return `Result`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
enum AppError {
|
|
||||||
#[error(transparent)]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Network(#[from] reqwest::Error),
|
|
||||||
#[error("{0}")]
|
|
||||||
Custom(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::Serialize for AppError {
|
|
||||||
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
|
||||||
where S: serde::ser::Serializer {
|
|
||||||
s.serialize_str(&self.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn my_command() -> Result<String, AppError> {
|
|
||||||
let data = std::fs::read_to_string("config.toml")?;
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### R2. Frontend Error Handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await invoke('my_command', { arg: 'value' });
|
|
||||||
console.log('Success:', result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Command failed:', error);
|
|
||||||
// Show user-friendly error in UI
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### R3. Structured Errors for the Frontend
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
#[serde(tag = "kind", content = "message")]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
enum ErrorKind {
|
|
||||||
Io(String),
|
|
||||||
Network(String),
|
|
||||||
Custom(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::Serialize for AppError {
|
|
||||||
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
|
||||||
where S: serde::ser::Serializer {
|
|
||||||
let kind = match self {
|
|
||||||
Self::Io(e) => ErrorKind::Io(e.to_string()),
|
|
||||||
Self::Network(e) => ErrorKind::Network(e.to_string()),
|
|
||||||
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
|
|
||||||
};
|
|
||||||
kind.serialize(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
|
|
||||||
|
|
||||||
### R4. Cleanup Event Listeners
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { listen } from '@tauri-apps/api/event';
|
|
||||||
|
|
||||||
// ❌ WRONG — memory leak
|
|
||||||
useEffect(() => {
|
|
||||||
listen('my-event', handler);
|
|
||||||
return () => {}; // No cleanup!
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// ✅ CORRECT — proper cleanup
|
|
||||||
useEffect(() => {
|
|
||||||
const unlisten = listen('my-event', handler);
|
|
||||||
return () => { unlisten.then(fn => fn()); };
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick-Reference: Top 25 Most Common Errors
|
|
||||||
|
|
||||||
| # | Error Message | Category | Quick Fix |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 1 | `X not allowed. Plugin not found` | Permission | Add permissions to `capabilities/default.json` |
|
|
||||||
| 2 | `Additional properties not allowed ('devPath', 'distDir')` | Config | Rename to `devUrl` / `frontendDist` |
|
|
||||||
| 3 | `__TAURI__ is not defined` | Frontend | Set `withGlobalTauri: true` or use npm imports |
|
|
||||||
| 4 | `AssetNotFound("index.html")` | Config | Fix `frontendDist` path to match bundler output |
|
|
||||||
| 5 | `unresolved import 'tauri::api'` | Migration | Replace with plugins (`tauri-plugin-*`) |
|
|
||||||
| 6 | `cannot find type 'Window' in 'tauri'` | Migration | Use `WebviewWindow` |
|
|
||||||
| 7 | `failed to create webview: WebView2 error` | Webview | Install WebView2; remove custom browser args |
|
|
||||||
| 8 | `Path not allowed on the configured scope` | Permission | Add FS scope permissions |
|
|
||||||
| 9 | `command not found` | IPC | Add command to `generate_handler![]` |
|
|
||||||
| 10 | `cannot find type 'SystemTray'` | Migration | Use `tauri::tray::TrayIconBuilder` |
|
|
||||||
| 11 | `lifetime not constrained` in async | Async | Use owned types (`String`, not `&str`) |
|
|
||||||
| 12 | `MutexGuard cannot be sent between threads` | Async | Use `tokio::sync::Mutex` instead of `std::sync::Mutex` |
|
|
||||||
| 13 | `xcodebuild exited with code 65` | Mobile | Set signing team in Xcode |
|
|
||||||
| 14 | `can't find crate for core` | Mobile | `rustup target add <target>` |
|
|
||||||
| 15 | `Additional properties not allowed ('allowlist')` | Config | Remove; use v2 capabilities system |
|
|
||||||
| 16 | `beforeDevCommand terminated with non-zero status` | Config | Fix npm scripts and port numbers |
|
|
||||||
| 17 | Vite infinite recompilation | Frontend | Add `src-tauri` to `watch.ignored` |
|
|
||||||
| 18 | Next.js `useEffect` broken in production | Frontend | Use `output: "export"` + `'use client'` |
|
|
||||||
| 19 | `program not allowed on shell scope` | Shell | Add program to shell capability scope |
|
|
||||||
| 20 | `Refused to connect to 'http://ipc.localhost'` | CSP | Add `ipc: http://ipc.localhost` to `connect-src` |
|
|
||||||
| 21 | Only last `invoke_handler` works | IPC | Combine all commands in one `generate_handler![]` |
|
|
||||||
| 22 | `GDK may only be used from the main thread` | Platform | Update Tauri; avoid cross-thread UI calls |
|
|
||||||
| 23 | `PluginInitialization("http", "invalid type")` | Config | Migrate old plugins config to v2 capabilities |
|
|
||||||
| 24 | Sidecar `Io(Os { code: 3 })` | Shell | Fix binary naming with target triple suffix |
|
|
||||||
| 25 | `error while running tauri application` | Generic | Check inner error; improve error handling |
|
|
||||||
930
tauri2-guide/01-core-architecture-config-commands-ipc.md
Normal file
930
tauri2-guide/01-core-architecture-config-commands-ipc.md
Normal file
@ -0,0 +1,930 @@
|
|||||||
|
# Tauri 2.0 Developer Guide — Part 1: Core Architecture, Config, Commands & IPC
|
||||||
|
|
||||||
|
> **Version**: 2.0 | **Last Updated**: 2026-05-31
|
||||||
|
> **Official Docs**: https://v2.tauri.app
|
||||||
|
>
|
||||||
|
> This file covers Sections 1-7 of the Definitive Guide. Part 2 covers Windows, Plugins, WebSocket, State, Migration & Anti-Patterns.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Introduction & Architecture Overview](#1-introduction--architecture-overview)
|
||||||
|
2. [Project Structure & Scaffolding](#2-project-structure--scaffolding)
|
||||||
|
3. [Configuration System (`tauri.conf.json`)](#3-configuration-system-tauriconfjson)
|
||||||
|
4. [Capabilities & Permissions (Security Model)](#4-capabilities--permissions-security-model)
|
||||||
|
5. [Rust Backend: `lib.rs` & Commands](#5-rust-backend-librs--commands)
|
||||||
|
6. [Frontend Integration Patterns](#6-frontend-integration-patterns)
|
||||||
|
7. [IPC: Commands, Events & Channels](#7-ipc-commands-events--channels)
|
||||||
|
|
||||||
|
**See also**: [Part 2: Windows, Plugins, WebSocket, State & Migration](./02-windows-plugins-websocket-state-migration.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
## 1. Introduction & Architecture Overview
|
||||||
|
|
||||||
|
Tauri 2.0 is a framework for building small, fast binaries for all major desktop and mobile platforms. It uses a **hybrid architecture** where a Rust backend handles system-level operations while a web frontend (HTML/CSS/JS, or any framework like React/Vue/Svelte) renders the UI inside a native OS webview.
|
||||||
|
|
||||||
|
### Core Architecture Principles
|
||||||
|
|
||||||
|
- **Rust Core (`src-tauri/`)**: Handles system access, file operations, network calls, window management, and any heavy computation. All system interactions MUST go through Rust to maintain security boundaries.
|
||||||
|
- **Web Frontend (`src/`)**: Renders the UI using standard web technologies. Communicates with Rust exclusively through Tauri's IPC mechanisms (`invoke`, events, channels).
|
||||||
|
- **Security Boundary**: The frontend runs in a sandboxed webview. It cannot directly access the filesystem, network, or OS APIs — it must request these through Tauri commands and plugins, which are governed by the **Capabilities & Permissions** system.
|
||||||
|
- **Mobile Support (NEW in v2)**: Tauri 2.0 adds first-class iOS and Android support. This requires a specific project structure (`lib.rs` + `main.rs` pattern) and platform-specific capability configurations.
|
||||||
|
|
||||||
|
### What Changed from v1 to v2
|
||||||
|
|
||||||
|
Tauri 2.0 is a **complete architectural overhaul**, not an incremental update. The most significant changes are:
|
||||||
|
|
||||||
|
| Area | v1 | v2 |
|
||||||
|
|------|----|----|
|
||||||
|
| Security | `allowlist` in config | **Capabilities + Permissions** (ACL-based) |
|
||||||
|
| APIs | Built-in `tauri::api` module | **Everything is a plugin** |
|
||||||
|
| Window type | `Window` / `WindowBuilder` | **`WebviewWindow` / `WebviewWindowBuilder`** |
|
||||||
|
| Config structure | Nested `tauri >` key | Flattened: `app`, `bundle`, `build` |
|
||||||
|
| Event system | Per-window scoped | **`emit()` is global; `emit_to()` for targeting** |
|
||||||
|
| JS imports | `@tauri-apps/api/tauri` | **`@tauri-apps/api/core`** |
|
||||||
|
| Mobile support | Community plugins | **First-class iOS/Android support** |
|
||||||
|
| Project structure | `main.rs` only | **`lib.rs` + `main.rs` pattern required** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Project Structure & Scaffolding
|
||||||
|
|
||||||
|
### Standard Tauri 2.0 File Hierarchy
|
||||||
|
|
||||||
|
```text
|
||||||
|
my-app/
|
||||||
|
├── src/ # Frontend UI source
|
||||||
|
│ ├── index.html # Main HTML entry
|
||||||
|
│ ├── main.js # Frontend JavaScript
|
||||||
|
│ └── styles.css # Styles
|
||||||
|
├── src-tauri/ # Rust backend environment
|
||||||
|
│ ├── capabilities/ # Security capability definitions
|
||||||
|
│ │ └── default.json # Maps windows → permissions
|
||||||
|
│ ├── icons/ # App icons for all platforms
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── main.rs # Minimal desktop entry-point
|
||||||
|
│ │ ├── lib.rs # Core application setup & commands
|
||||||
|
│ │ └── <modules>.rs # Additional Rust modules
|
||||||
|
│ ├── build.rs # Tauri build script
|
||||||
|
│ ├── Cargo.toml # Rust dependencies
|
||||||
|
│ ├── Cargo.lock # Lockfile (COMMIT THIS)
|
||||||
|
│ └── tauri.conf.json # Main Tauri configuration
|
||||||
|
├── package.json # Node.js dependencies & scripts
|
||||||
|
└── package-lock.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a New Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using the interactive CLI scaffolder
|
||||||
|
npm create tauri-app@latest
|
||||||
|
|
||||||
|
# Or with specific template
|
||||||
|
npm create tauri-app@latest -- --template vanilla-ts
|
||||||
|
|
||||||
|
# Cargo alternative
|
||||||
|
cargo create-tauri-app
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported templates: Vanilla, Vue, Svelte, React, Solid, Angular, Preact, Yew, Leptos, Sycamore.
|
||||||
|
|
||||||
|
### Adding Tauri to an Existing Frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D @tauri-apps/cli@latest
|
||||||
|
npx tauri init
|
||||||
|
```
|
||||||
|
|
||||||
|
The `init` command will prompt for: app name, window title, web assets location, dev server URL, and frontend build/dev commands.
|
||||||
|
|
||||||
|
### The `lib.rs` + `main.rs` Pattern (Required in v2)
|
||||||
|
|
||||||
|
Tauri 2.0 requires your core application logic to live in `lib.rs`, with a minimal `main.rs` that calls into it. This is **mandatory** for mobile support because mobile platforms require a shared library entry-point rather than a standard `main()` function.
|
||||||
|
|
||||||
|
**`src-tauri/src/main.rs`** — Desktop-only entry-point:
|
||||||
|
```rust
|
||||||
|
// Prevents additional console window on Windows in release mode
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
my_app_lib::run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src-tauri/src/lib.rs`** — Core application:
|
||||||
|
```rust
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
// plugins, commands, setup, etc.
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src-tauri/Cargo.toml`** — Must declare the library:
|
||||||
|
```toml
|
||||||
|
[lib]
|
||||||
|
name = "my_app_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
```
|
||||||
|
|
||||||
|
The `_lib` suffix in the crate name prevents naming conflicts with the binary on Windows. The three crate types cover: static linking (mobile), dynamic linking (mobile), and Rust library usage (desktop/tests).
|
||||||
|
|
||||||
|
### Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run in development mode (hot-reloads UI + auto-recompiles Rust)
|
||||||
|
npm run tauri dev
|
||||||
|
# or: pnpm tauri dev, bun tauri dev, cargo tauri dev
|
||||||
|
|
||||||
|
# Build production artifacts
|
||||||
|
npm run tauri build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Configuration System (`tauri.conf.json`)
|
||||||
|
|
||||||
|
### Main Configuration File
|
||||||
|
|
||||||
|
The primary configuration file is `src-tauri/tauri.conf.json`. Tauri 2.0 supports three formats:
|
||||||
|
|
||||||
|
| Format | Feature Flag Required |
|
||||||
|
|--------|-----------------------|
|
||||||
|
| **JSON** (default) | None |
|
||||||
|
| **JSON5** | `config-json5` on both `tauri` and `tauri-build` |
|
||||||
|
| **TOML** | `config-toml` on both `tauri` and `tauri-build` |
|
||||||
|
|
||||||
|
### Complete v2 Configuration Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://tauri.app",
|
||||||
|
"productName": "my-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"identifier": "com.mycompany.myapp",
|
||||||
|
"build": {
|
||||||
|
"frontendDist": "../dist",
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"beforeDevCommand": "npm run dev",
|
||||||
|
"beforeBuildCommand": "npm run build"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"withGlobalTauri": true,
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"label": "main",
|
||||||
|
"title": "My App",
|
||||||
|
"width": 800,
|
||||||
|
"height": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"center": true,
|
||||||
|
"dragDropEnabled": true,
|
||||||
|
"useHttpsScheme": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'",
|
||||||
|
"assetProtocol": {
|
||||||
|
"scope": ["$APPDATA/**", "$RESOURCE/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"licenseFile": "LICENSE",
|
||||||
|
"copyright": "",
|
||||||
|
"category": "Utility"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Configuration Changes from v1
|
||||||
|
|
||||||
|
| v1 Path | v2 Path | Notes |
|
||||||
|
|---------|---------|-------|
|
||||||
|
| `package.productName` | Top-level `productName` | Moved out of `package` |
|
||||||
|
| `package.version` | Top-level `version` | Moved out of `package` |
|
||||||
|
| `package` | *(removed)* | Fields redistributed |
|
||||||
|
| `build.distDir` | `build.frontendDist` | Renamed |
|
||||||
|
| `build.devPath` | `build.devUrl` | Renamed |
|
||||||
|
| `build.withGlobalTauri` | `app.withGlobalTauri` | Moved to `app` section |
|
||||||
|
| `tauri.*` | `app.*` | Top-level key renamed |
|
||||||
|
| `tauri.allowlist` | *(removed)* | Replaced by capabilities |
|
||||||
|
| `tauri.windows.fileDropEnabled` | `app.windows.dragDropEnabled` | Renamed |
|
||||||
|
| `tauri.bundle` | Top-level `bundle` | Promoted to top-level |
|
||||||
|
| `tauri.updater` | `plugins.updater` | Moved to plugins |
|
||||||
|
| `tauri.systemTray` | `app.trayIcon` | Renamed |
|
||||||
|
| `tauri.cli` | `plugins.cli` | Moved to plugins |
|
||||||
|
| `bundle.identifier` | Top-level `identifier` | Promoted to top-level |
|
||||||
|
|
||||||
|
### Platform-Specific Configuration
|
||||||
|
|
||||||
|
Create platform override files that **merge** with the main config using JSON Merge Patch (RFC 7396). Arrays are replaced entirely (not element-by-element merged).
|
||||||
|
|
||||||
|
| Platform | File Pattern |
|
||||||
|
|----------|-------------|
|
||||||
|
| Linux | `tauri.linux.conf.json` or `Tauri.linux.toml` |
|
||||||
|
| Windows | `tauri.windows.conf.json` or `Tauri.windows.toml` |
|
||||||
|
| macOS | `tauri.macos.conf.json` or `Tauri.macos.toml` |
|
||||||
|
| Android | `tauri.android.conf.json` or `Tauri.android.toml` |
|
||||||
|
| iOS | `tauri.ios.conf.json` or `Tauri.ios.toml` |
|
||||||
|
|
||||||
|
### Cargo.toml Dependencies
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Keep `tauri`, `tauri-build`, and `@tauri-apps/cli` on the same minor version. Always commit `Cargo.lock` for reproducible builds.
|
||||||
|
|
||||||
|
### package.json Scripts
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"tauri": "tauri",
|
||||||
|
"dev": "tauri dev",
|
||||||
|
"build": "tauri build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"tauri"` script is only required when using `npm` (not needed for yarn/pnpm/bun).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Capabilities & Permissions (Security Model)
|
||||||
|
|
||||||
|
Tauri 2.0 introduces a strict **Access Control List (ACL)** security model that replaces the v1 `allowlist`. This is the most important new concept to understand.
|
||||||
|
|
||||||
|
### Core Concepts
|
||||||
|
|
||||||
|
- **Capabilities** define **which permissions** are granted to **which windows or webviews**.
|
||||||
|
- **Permissions** describe explicit privileges for specific commands (e.g., `fs:allow-read`, `http:allow-request`).
|
||||||
|
- **Scopes** further restrict what a permission can access (e.g., only certain file paths).
|
||||||
|
- Multiple capabilities can apply to the same window — permissions **merge**.
|
||||||
|
- Security boundaries are based on **window labels** (not titles).
|
||||||
|
|
||||||
|
### Capability File Location
|
||||||
|
|
||||||
|
```
|
||||||
|
src-tauri/capabilities/<identifier>.json (or .toml)
|
||||||
|
```
|
||||||
|
|
||||||
|
All files in this directory are **automatically loaded** by default.
|
||||||
|
|
||||||
|
### Basic Capability File
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "main-window-capability",
|
||||||
|
"description": "Permissions for the primary application window",
|
||||||
|
"platforms": ["linux", "macOS", "windows"],
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field reference:**
|
||||||
|
- `$schema` — Points to generated schema for IDE autocompletion. Use `desktop-schema.json` for desktop, `mobile-schema.json` for mobile.
|
||||||
|
- `identifier` — Unique capability name (ASCII lowercase, max 116 chars).
|
||||||
|
- `description` — Human-readable purpose.
|
||||||
|
- `windows` — Array of window labels this capability applies to. Use `["*"]` for all windows.
|
||||||
|
- `permissions` — Array of permission identifiers.
|
||||||
|
- `platforms` — Optional. Restricts to specific platforms. Defaults to all platforms if omitted.
|
||||||
|
|
||||||
|
### Permission Identifier Naming Convention
|
||||||
|
|
||||||
|
| Pattern | Meaning | Example |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `<plugin>:default` | Default permission set for a plugin | `fs:default` |
|
||||||
|
| `<plugin>:allow-<command>` | Allow a specific command | `fs:allow-read` |
|
||||||
|
| `<plugin>:deny-<command>` | Deny a specific command | `fs:deny-write` |
|
||||||
|
| `core:<module>:<permission>` | Core Tauri module permission | `core:event:allow-emit` |
|
||||||
|
|
||||||
|
### Common Permission Identifiers
|
||||||
|
|
||||||
|
**Core permissions:**
|
||||||
|
- `core:default` — Basic runtime permissions
|
||||||
|
- `core:path:default` — Path resolution
|
||||||
|
- `core:event:default` — Event system (includes listen/emit)
|
||||||
|
- `core:event:allow-listen` — Allow listening to events
|
||||||
|
- `core:event:allow-emit` — Allow emitting events
|
||||||
|
- `core:window:default` — Window management
|
||||||
|
- `core:window:allow-set-title` — Allow changing window title
|
||||||
|
|
||||||
|
**Plugin permissions:**
|
||||||
|
- `fs:allow-read`, `fs:allow-write`, `fs:allow-exists`, `fs:allow-mkdir`, `fs:allow-remove`
|
||||||
|
- `http:allow-request` — HTTP client requests
|
||||||
|
- `dialog:allow-open`, `dialog:allow-save`, `dialog:allow-message`
|
||||||
|
- `shell:allow-open`, `shell:allow-execute`
|
||||||
|
- `notification:allow-notify`, `notification:allow-is-permission-granted`
|
||||||
|
- `clipboard-manager:allow-read-text`, `clipboard-manager:allow-write-text`
|
||||||
|
- `websocket:allow-connect`, `websocket:allow-send`
|
||||||
|
- `store:default` — Persistent key-value store
|
||||||
|
|
||||||
|
### Fine-Grained Scopes
|
||||||
|
|
||||||
|
Scopes restrict what a permission can actually access:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"identifier": "secure-fs-capability",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"identifier": "fs:allow-read",
|
||||||
|
"allow": [{ "path": "$APPDATA/logs/*" }, { "path": "$HOME/Documents/*.txt" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "fs:allow-write",
|
||||||
|
"allow": [{ "path": "$APPDATA/logs/*.log" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "fs:deny-write",
|
||||||
|
"deny": [{ "path": "$APPDATA/config/*" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available scope variables: `$APPDATA`, `$HOME`, `$APPCONFIG`, `$APPCACHE`, `$APPLOG`, `$RESOURCE`, `$EXE`.
|
||||||
|
|
||||||
|
### Restricting Custom Commands
|
||||||
|
|
||||||
|
By default, all `#[tauri::command]` functions are accessible from all windows. To restrict a custom command to specific windows only, use `build.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
tauri_build::try_build(
|
||||||
|
tauri_build::Attributes::new()
|
||||||
|
.app_manifest(
|
||||||
|
tauri_build::AppManifest::new()
|
||||||
|
.commands(&["my_restricted_command", "admin_only_command"])
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then grant access in the capability:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"identifier": "admin-capability",
|
||||||
|
"windows": ["admin-panel"],
|
||||||
|
"permissions": [
|
||||||
|
{ "identifier": "allow-my-restricted-command" },
|
||||||
|
{ "identifier": "allow-admin-only-command" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Referencing Capabilities Explicitly
|
||||||
|
|
||||||
|
By default, all capability files in `src-tauri/capabilities/` are auto-discovered. To control which ones are used explicitly:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"security": {
|
||||||
|
"capabilities": ["my-capability", "admin-capability"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline Capabilities
|
||||||
|
|
||||||
|
You can define capabilities directly in `tauri.conf.json` instead of separate files:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"security": {
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"identifier": "inline-cap",
|
||||||
|
"windows": ["*"],
|
||||||
|
"permissions": ["core:default"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Rust Backend: `lib.rs` & Commands
|
||||||
|
|
||||||
|
### Application Builder Pattern
|
||||||
|
|
||||||
|
All Tauri 2.0 applications follow the `tauri::Builder` pattern:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_opener::init()) // Register plugins
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_fs::init())
|
||||||
|
.setup(|app| { // Setup hook (runs once at startup)
|
||||||
|
// Initialize resources, spawn servers, etc.
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.manage(AppState { /* ... */ }) // Inject managed state
|
||||||
|
.invoke_handler(tauri::generate_handler![ // Register commands
|
||||||
|
my_command,
|
||||||
|
another_command,
|
||||||
|
module::command_in_module
|
||||||
|
])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical rules:**
|
||||||
|
- `invoke_handler` can only be called **once**. All commands must be in a single `generate_handler![]` call.
|
||||||
|
- Commands defined in `lib.rs` **cannot** be marked `pub`.
|
||||||
|
- Commands defined in separate modules **must** be marked `pub`.
|
||||||
|
- The `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is required for the `run()` function.
|
||||||
|
|
||||||
|
### Defining Commands
|
||||||
|
|
||||||
|
Commands are the primary IPC mechanism. They are Rust functions annotated with `#[tauri::command]` and callable from the frontend via `invoke()`.
|
||||||
|
|
||||||
|
#### Basic Command
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tauri::command]
|
||||||
|
fn greet(name: String) -> String {
|
||||||
|
format!("Hello, {}! Welcome from Rust.", name)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Command with Error Handling
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tauri::command]
|
||||||
|
fn login(user: String, password: String) -> Result<String, String> {
|
||||||
|
if user == "admin" && password == "secret" {
|
||||||
|
Ok("authenticated".to_string())
|
||||||
|
} else {
|
||||||
|
Err("invalid credentials".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Async Command
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tauri::command]
|
||||||
|
async fn fetch_data(url: String) -> Result<String, String> {
|
||||||
|
let response = reqwest::get(&url)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
response.text().await.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**IMPORTANT limitation**: Async commands cannot accept borrowed references (`&str`, `&Path`) directly. Convert to owned types (`String`, `PathBuf`) or wrap the return in `Result`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Option A: Use owned types
|
||||||
|
#[tauri::command]
|
||||||
|
async fn process(value: String) -> String { /* ... */ }
|
||||||
|
|
||||||
|
// Option B: Wrap return in Result
|
||||||
|
#[tauri::command]
|
||||||
|
async fn process(value: &str) -> Result<String, ()> { /* ... */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Error Types (Recommended)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum AppError {
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Network error: {0}")]
|
||||||
|
Network(#[from] reqwest::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement Serialize so the error can cross the IPC boundary
|
||||||
|
impl serde::Serialize for AppError {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.to_string().as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn read_config() -> Result<String, AppError> {
|
||||||
|
let content = std::fs::read_to_string("config.toml")?;
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Structured Error with Kind/Tag
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum AppError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
#[serde(tag = "kind", content = "message")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
enum ErrorKind {
|
||||||
|
Io(String),
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for AppError {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
let kind = match self {
|
||||||
|
Self::Io(e) => ErrorKind::Io(e.to_string()),
|
||||||
|
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
|
||||||
|
};
|
||||||
|
kind.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
|
||||||
|
|
||||||
|
#### Accessing Special Objects in Commands
|
||||||
|
|
||||||
|
**AppHandle** — Access to the application instance:
|
||||||
|
```rust
|
||||||
|
#[tauri::command]
|
||||||
|
fn do_something(app_handle: tauri::AppHandle) {
|
||||||
|
let app_dir = app_handle.path().app_dir();
|
||||||
|
// Spawn windows, emit events, access state...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**WebviewWindow** — The window that invoked the command:
|
||||||
|
```rust
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_window_info(webview_window: tauri::WebviewWindow) -> String {
|
||||||
|
webview_window.label().to_string()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Managed State** — Shared application state:
|
||||||
|
```rust
|
||||||
|
use tauri::State;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
struct DbConnection(Mutex<String>);
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn query_database(state: State<'_, DbConnection>) -> String {
|
||||||
|
let conn = state.0.lock().unwrap();
|
||||||
|
conn.clone()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Raw Request** — Access headers and raw body:
|
||||||
|
```rust
|
||||||
|
#[tauri::command]
|
||||||
|
fn upload(request: tauri::ipc::Request) -> Result<(), String> {
|
||||||
|
let tauri::ipc::InvokeBody::Raw(data) = request.body() else {
|
||||||
|
return Err("Expected raw body".to_string());
|
||||||
|
};
|
||||||
|
let auth = request.headers().get("Authorization")
|
||||||
|
.ok_or("Missing auth header")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Commands in Separate Modules
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src-tauri/src/commands.rs
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn create_user(name: String) -> String {
|
||||||
|
format!("Created user: {}", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_user(id: u32) -> bool {
|
||||||
|
// ...
|
||||||
|
true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src-tauri/src/lib.rs
|
||||||
|
mod commands;
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
commands::create_user,
|
||||||
|
commands::delete_user
|
||||||
|
])
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The `commands::` prefix is Rust path resolution only — the frontend still calls `invoke('create_user', ...)` without any prefix.
|
||||||
|
|
||||||
|
### Returning Large Data (ArrayBuffers)
|
||||||
|
|
||||||
|
For large binary data like file contents, use `tauri::ipc::Response`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::ipc::Response;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn read_file() -> Response {
|
||||||
|
let data = std::fs::read("/path/to/file").unwrap();
|
||||||
|
Response::new(data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Streaming Data (Channels)
|
||||||
|
|
||||||
|
For streaming large amounts of data to the frontend:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::ipc::Channel;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn stream_file(path: std::path::PathBuf, on_chunk: Channel<Vec<u8>>) {
|
||||||
|
let mut file = tokio::fs::File::open(path).await.unwrap();
|
||||||
|
let mut buf = vec![0u8; 4096];
|
||||||
|
loop {
|
||||||
|
let n = file.read(&mut buf).await.unwrap();
|
||||||
|
if n == 0 { break; }
|
||||||
|
on_chunk.send(&buf[..n]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Frontend Integration Patterns
|
||||||
|
|
||||||
|
### Using `@tauri-apps/api` npm Package (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @tauri-apps/api
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Imports in v2 — NOTE the path changes from v1
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { listen, emit, emitTo, once } from '@tauri-apps/api/event';
|
||||||
|
import { getCurrentWebviewWindow, WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import { Channel } from '@tauri-apps/api/core';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Global `window.__TAURI__` (No npm package needed)
|
||||||
|
|
||||||
|
Requires `app.withGlobalTauri: true` in `tauri.conf.json`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// No import needed — accessed via the global object
|
||||||
|
const { invoke } = window.__TAURI__.core;
|
||||||
|
const { listen, emit } = window.__TAURI__.event;
|
||||||
|
```
|
||||||
|
|
||||||
|
This pattern is ideal for vanilla JS projects that don't use a bundler, avoiding the need for any npm packages beyond `@tauri-apps/cli`.
|
||||||
|
|
||||||
|
### Import Path Changes from v1
|
||||||
|
|
||||||
|
| v1 Import | v2 Import |
|
||||||
|
|-----------|-----------|
|
||||||
|
| `@tauri-apps/api/tauri` | `@tauri-apps/api/core` |
|
||||||
|
| `@tauri-apps/api/window` | `@tauri-apps/api/webviewWindow` |
|
||||||
|
| `@tauri-apps/api/cli` | `@tauri-apps/plugin-cli` |
|
||||||
|
| `@tauri-apps/api/clipboard` | `@tauri-apps/plugin-clipboard-manager` |
|
||||||
|
| `@tauri-apps/api/dialog` | `@tauri-apps/plugin-dialog` |
|
||||||
|
| `@tauri-apps/api/fs` | `@tauri-apps/plugin-fs` |
|
||||||
|
| `@tauri-apps/api/global-shortcut` | `@tauri-apps/plugin-global-shortcut` |
|
||||||
|
| `@tauri-apps/api/http` | `@tauri-apps/plugin-http` |
|
||||||
|
| `@tauri-apps/api/notification` | `@tauri-apps/plugin-notification` |
|
||||||
|
| `@tauri-apps/api/shell` | `@tauri-apps/plugin-shell` |
|
||||||
|
| `@tauri-apps/api/updater` | `@tauri-apps/plugin-updater` |
|
||||||
|
| `@tauri-apps/api/os` | `@tauri-apps/plugin-os` |
|
||||||
|
| `@tauri-apps/api/process` | `@tauri-apps/plugin-process` |
|
||||||
|
|
||||||
|
**Rule of thumb in v2**: If it's not `core`, `event`, or `webviewWindow`, it's a plugin.
|
||||||
|
|
||||||
|
### Plugin JS Package Naming
|
||||||
|
|
||||||
|
All v2 plugin JS packages follow: `@tauri-apps/plugin-<name>`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @tauri-apps/plugin-store
|
||||||
|
npm install @tauri-apps/plugin-fs
|
||||||
|
npm install @tauri-apps/plugin-http
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. IPC: Commands, Events & Channels
|
||||||
|
|
||||||
|
### Commands (Frontend → Rust)
|
||||||
|
|
||||||
|
The primary request-response mechanism:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Frontend (v2 import)
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
|
// Simple invocation
|
||||||
|
const result = await invoke('greet', { name: 'World' });
|
||||||
|
console.log(result); // "Hello, World! Welcome from Rust."
|
||||||
|
|
||||||
|
// With error handling
|
||||||
|
try {
|
||||||
|
const token = await invoke('login', { user: 'admin', password: 'secret' });
|
||||||
|
console.log('Authenticated:', token);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login failed:', error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Argument naming convention**: Arguments are passed as a JSON object with **camelCase** keys by default. Use `#[tauri::command(rename_all = "snake_case")]` to accept snake_case from the frontend.
|
||||||
|
|
||||||
|
### Events (Bidirectional, Multi-Consumer)
|
||||||
|
|
||||||
|
Events are fire-and-forget messages. Use them for notifications, streaming status updates, or any data that multiple components might need.
|
||||||
|
|
||||||
|
#### Emitting Events from Rust
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::{AppHandle, Emitter};
|
||||||
|
|
||||||
|
// Global event (all listeners receive it)
|
||||||
|
app_handle.emit("download-progress", 42)?;
|
||||||
|
|
||||||
|
// Target a specific window
|
||||||
|
app_handle.emit_to("settings-panel", "config-changed", new_config)?;
|
||||||
|
|
||||||
|
// Filter to specific windows
|
||||||
|
use tauri::EventTarget;
|
||||||
|
app_handle.emit_filter("notification", payload, |target| {
|
||||||
|
matches!(target, EventTarget::WebviewWindow { label } if label == "main")
|
||||||
|
})?;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Listening for Events on the Frontend
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { listen, once, emit, emitTo } from '@tauri-apps/api/event';
|
||||||
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
|
||||||
|
// Global listen (receives ALL events with this name)
|
||||||
|
const unlisten = await listen('download-progress', (event) => {
|
||||||
|
console.log(`Progress: ${event.payload}%`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Targeted listen (only events emitted to THIS window)
|
||||||
|
const appWebview = getCurrentWebviewWindow();
|
||||||
|
const unlisten2 = await appWebview.listen('config-changed', (event) => {
|
||||||
|
console.log('New config:', event.payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen once, then auto-cleanup
|
||||||
|
await once('initialization-complete', (event) => {
|
||||||
|
console.log('App ready!', event.payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit from frontend to Rust
|
||||||
|
await emit('user-action', { type: 'click', target: 'button' });
|
||||||
|
|
||||||
|
// Emit to a specific window from frontend
|
||||||
|
const target = new WebviewWindow('settings-panel');
|
||||||
|
await target.emit('settings-request', { key: 'theme' });
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always call `unlisten()`** when the component unmounts or the listener is no longer needed to prevent memory leaks.
|
||||||
|
|
||||||
|
#### Listening for Events on the Rust Side
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::{Listener, Manager};
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
// Listen globally
|
||||||
|
app.listen("user-action", |event| {
|
||||||
|
println!("Received: {}", event.payload());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen on a specific window
|
||||||
|
let main = app.get_webview_window("main").unwrap();
|
||||||
|
main.listen("config-changed", |event| {
|
||||||
|
println!("Config updated: {}", event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen once
|
||||||
|
app.once("ready", |event| {
|
||||||
|
println!("App is ready!");
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channels (High-Throughput Streaming)
|
||||||
|
|
||||||
|
Channels are the recommended mechanism for streaming large amounts of ordered data from Rust to the frontend. They are faster and more memory-efficient than events for high-throughput scenarios.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::ipc::Channel;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
#[serde(tag = "event", content = "data")]
|
||||||
|
enum StreamEvent {
|
||||||
|
Started { total: usize },
|
||||||
|
Progress { current: usize, chunk: Vec<u8> },
|
||||||
|
Finished { total: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn download_file(url: String, on_event: Channel<StreamEvent>) {
|
||||||
|
on_event.send(StreamEvent::Started { total: 1000 }).unwrap();
|
||||||
|
// ... streaming logic ...
|
||||||
|
on_event.send(StreamEvent::Finished { total: 1000 }).unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { invoke, Channel } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
|
const onEvent = new Channel();
|
||||||
|
onEvent.onmessage = (event) => {
|
||||||
|
switch (event.event) {
|
||||||
|
case 'started': console.log(`Starting: ${event.data.total} bytes`); break;
|
||||||
|
case 'progress': /* handle chunk */ break;
|
||||||
|
case 'finished': console.log('Done!'); break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await invoke('download_file', { url: 'https://...', onEvent });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Evaluating JavaScript from Rust
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
// In a command or setup hook
|
||||||
|
let webview = app.get_webview_window("main").unwrap();
|
||||||
|
webview.eval("document.getElementById('status').textContent = 'Loaded!'")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
For complex data passing, use the `serialize-to-javascript` crate.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Continue to**: [Part 2: Windows, Plugins, WebSocket, State & Migration](./02-windows-plugins-websocket-state-migration.md)
|
||||||
217
tauri2-guide/01-system-prompt-and-common-prompts.md
Normal file
217
tauri2-guide/01-system-prompt-and-common-prompts.md
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# Tauri 2.0 Agent Prompts — Part 1: System Prompt & Common Task Prompts
|
||||||
|
|
||||||
|
> **Purpose**: Copy-paste ready prompts for instructing AI agents to build Tauri 2.0 applications.
|
||||||
|
> **Constraint**: These prompts are designed for sandbox environments where code compilation is NOT available. Agents must verify correctness using logic, API references, and pattern matching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Universal System Prompt (Required Prefix)](#1-universal-system-prompt-required-prefix)
|
||||||
|
2. [Prompt: Scaffold a New Tauri 2.0 Project](#2-prompt-scaffold-a-new-tauri-20-project)
|
||||||
|
3. [Prompt: Add a Tauri Plugin](#3-prompt-add-a-tauri-plugin)
|
||||||
|
4. [Prompt: Create a Custom Command (IPC)](#4-prompt-create-a-custom-command-ipc)
|
||||||
|
5. [Prompt: Add State Management](#5-prompt-add-state-management)
|
||||||
|
6. [Prompt: Implement Event-Based Communication](#6-prompt-implement-event-based-communication)
|
||||||
|
7. [Prompt: Create Multi-Window App](#7-prompt-create-multi-window-app)
|
||||||
|
|
||||||
|
**See also**: [Part 2: Advanced Prompts, Checklist & Migration Map](./02-advanced-prompts-checklist-migration.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Universal System Prompt (Required Prefix)
|
||||||
|
|
||||||
|
> **Always include this as the system context before any task-specific prompt.**
|
||||||
|
|
||||||
|
```
|
||||||
|
You are a Tauri 2.0 expert developer. You build desktop and mobile applications using the Tauri 2.0 framework (Rust backend + web frontend).
|
||||||
|
|
||||||
|
CRITICAL ENVIRONMENT CONSTRAINTS:
|
||||||
|
- You CANNOT compile or run code in this sandbox. Do NOT attempt compilation.
|
||||||
|
- Verify correctness using logic, API pattern matching, and reference knowledge only.
|
||||||
|
- Cross-reference every code snippet against known Tauri 2.0 API patterns before outputting.
|
||||||
|
- If you are uncertain about an API or pattern, say so explicitly rather than guessing.
|
||||||
|
|
||||||
|
MANDATORY TAURI 2.0 RULES — NEVER VIOLATE:
|
||||||
|
1. This is Tauri 2.0 (NOT v1). The APIs are fundamentally different.
|
||||||
|
2. Use `tauri::WebviewWindow` (NOT `tauri::Window`).
|
||||||
|
3. Use `tauri::WebviewWindowBuilder` (NOT `tauri::WindowBuilder`).
|
||||||
|
4. Use `tauri::WebviewUrl` (NOT `tauri::WindowUrl`).
|
||||||
|
5. Use `Manager::get_webview_window()` (NOT `Manager::get_window()`).
|
||||||
|
6. Use `tauri::Emitter` trait for `emit()` and `emit_to()` (NOT the old window-only emit).
|
||||||
|
7. Use `tauri::Listener` trait for `listen()` and `once()` on the Rust side.
|
||||||
|
8. JS import `invoke` from `@tauri-apps/api/core` (NOT `@tauri-apps/api/tauri`).
|
||||||
|
9. JS import window utilities from `@tauri-apps/api/webviewWindow` (NOT `@tauri-apps/api/window`).
|
||||||
|
10. JS plugins import from `@tauri-apps/plugin-<name>` (NOT `@tauri-apps/api/<name>`).
|
||||||
|
11. Config uses `app` section (NOT `tauri` section), `build.frontendDist` (NOT `build.distDir`).
|
||||||
|
12. Security uses Capabilities + Permissions (NOT `allowlist`).
|
||||||
|
13. Every plugin requires: (a) Rust crate in Cargo.toml, (b) `.plugin()` registration in Builder, (c) npm package, (d) permissions in capabilities.
|
||||||
|
14. Use the `lib.rs` + `main.rs` pattern. Core logic goes in `lib.rs`. `main.rs` only calls `lib::run()`.
|
||||||
|
15. `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is REQUIRED on the `pub fn run()` function.
|
||||||
|
16. `invoke_handler` can only be called ONCE — all commands go in a single `tauri::generate_handler![]`.
|
||||||
|
17. Async commands cannot use borrowed references (`&str`, `&Path`) directly — use owned types or wrap return in `Result`.
|
||||||
|
18. Window creation from async contexts MUST use `app_handle.run_on_main_thread()`.
|
||||||
|
19. Always clean up event listeners (call `unlisten()`) on the frontend.
|
||||||
|
20. Use `tauri = { version = "2" }` and `tauri-build = { version = "2" }` (NOT version 1).
|
||||||
|
|
||||||
|
FILE STRUCTURE:
|
||||||
|
- Frontend source: `src/`
|
||||||
|
- Rust backend: `src-tauri/`
|
||||||
|
- Rust entry-point: `src-tauri/src/main.rs` (minimal)
|
||||||
|
- Core app logic: `src-tauri/src/lib.rs`
|
||||||
|
- Extra modules: `src-tauri/src/<module>.rs`
|
||||||
|
- Capabilities: `src-tauri/capabilities/default.json`
|
||||||
|
- Config: `src-tauri/tauri.conf.json`
|
||||||
|
- Rust deps: `src-tauri/Cargo.toml`
|
||||||
|
- Build script: `src-tauri/build.rs`
|
||||||
|
|
||||||
|
OFFICIAL DOCUMENTATION:
|
||||||
|
- Main docs: https://v2.tauri.app
|
||||||
|
- Rust API: https://docs.rs/tauri/latest/tauri/
|
||||||
|
- Migration guide: https://v2.tauri.app/start/migrate/from-tauri-1/
|
||||||
|
- Capabilities: https://v2.tauri.app/security/capabilities/
|
||||||
|
- Permissions: https://v2.tauri.app/security/permissions/
|
||||||
|
- Configuration: https://v2.tauri.app/develop/configuration-files/
|
||||||
|
- Calling Rust: https://v2.tauri.app/develop/calling-rust/
|
||||||
|
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
|
||||||
|
- Plugin index: https://v2.tauri.app/plugin/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Prompt: Scaffold a New Tauri 2.0 Project
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a new Tauri 2.0 project from scratch with vanilla JavaScript (no frontend framework).
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- App name: [APP_NAME]
|
||||||
|
- Window title: [WINDOW_TITLE]
|
||||||
|
- Default window size: [WIDTH]x[HEIGHT]
|
||||||
|
- withGlobalTauri enabled (for window.__TAURI__ access without npm package)
|
||||||
|
|
||||||
|
Generate ALL of the following files with complete, correct Tauri 2.0 code:
|
||||||
|
|
||||||
|
1. `package.json` — with @tauri-apps/cli v2 devDependency and scripts (dev, build, tauri)
|
||||||
|
2. `src/index.html` — basic HTML5 boilerplate
|
||||||
|
3. `src/main.js` — demonstrate invoking a Rust command
|
||||||
|
4. `src/styles.css` — basic styling
|
||||||
|
5. `src-tauri/tauri.conf.json` — correct v2 structure (app section, build section, bundle section)
|
||||||
|
6. `src-tauri/Cargo.toml` — with tauri v2, tauri-build v2, serde, serde_json. Include [lib] with crate-type ["staticlib", "cdylib", "rlib"]
|
||||||
|
7. `src-tauri/build.rs` — standard tauri_build::build()
|
||||||
|
8. `src-tauri/capabilities/default.json` — with core:default permission
|
||||||
|
9. `src-tauri/src/main.rs` — minimal entry-point calling lib::run()
|
||||||
|
10. `src-tauri/src/lib.rs` — with Builder pattern, mobile_entry_point attribute, a sample "greet" command, and the command registered in invoke_handler
|
||||||
|
|
||||||
|
Verify every line follows Tauri 2.0 conventions. Cross-check config structure against the v2 schema.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Prompt: Add a Tauri Plugin
|
||||||
|
|
||||||
|
```
|
||||||
|
Add the [PLUGIN_NAME] plugin to an existing Tauri 2.0 project.
|
||||||
|
|
||||||
|
Plugin to add: [tauri-plugin-STORE/turi-plugin-FS/etc.]
|
||||||
|
JS package name: [@tauri-apps/plugin-store/etc.]
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
1. Add the Rust crate to `src-tauri/Cargo.toml` as `tauri-plugin-[name] = "2"`
|
||||||
|
2. Register the plugin in `src-tauri/src/lib.rs` using `.plugin(tauri_plugin_[name]::init())` or the Builder pattern if the plugin requires configuration
|
||||||
|
3. Add the npm package: `@tauri-apps/plugin-[name]`
|
||||||
|
4. Update `src-tauri/capabilities/default.json` to add the appropriate permission (usually `[name]:default` or specific allow permissions)
|
||||||
|
5. Provide a frontend usage example showing the correct v2 import from `@tauri-apps/plugin-[name]`
|
||||||
|
|
||||||
|
Show the exact changes needed for each file. Do NOT use v1 import paths or API patterns.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Prompt: Create a Custom Command (IPC)
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a Tauri 2.0 command called "[COMMAND_NAME]" that [DESCRIBE WHAT IT DOES].
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Define the command using `#[tauri::command]` in a separate module file `src-tauri/src/commands.rs`
|
||||||
|
- The command must be `pub` (required for separate modules)
|
||||||
|
- Include proper error handling with `Result<T, String>` or a custom error type
|
||||||
|
- Register the command in `src-tauri/src/lib.rs` using `mod commands;` and including it in `generate_handler![commands::COMMAND_NAME]`
|
||||||
|
- Provide the frontend JavaScript code to invoke the command using `invoke()` from `@tauri-apps/api/core` or `window.__TAURI__.core`
|
||||||
|
- If the command is async, ensure all arguments are owned types (not borrowed references)
|
||||||
|
|
||||||
|
Show complete, correct code for both the Rust side and the JS side.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Prompt: Add State Management
|
||||||
|
|
||||||
|
```
|
||||||
|
Add shared state management to an existing Tauri 2.0 application.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Define a state struct in `src-tauri/src/lib.rs` using `std::sync::Mutex` for thread safety
|
||||||
|
2. Manage the state in the Builder using `.manage(MyState { ... })`
|
||||||
|
3. Create at least two commands that read and write the managed state using `tauri::State<'_, MyState>`
|
||||||
|
4. Show the frontend code for invoking these commands
|
||||||
|
|
||||||
|
If persistent storage is also needed:
|
||||||
|
- Add `tauri-plugin-store = "2"` to Cargo.toml
|
||||||
|
- Register the plugin
|
||||||
|
- Add `store:default` to capabilities
|
||||||
|
- Show frontend usage with `@tauri-apps/plugin-store`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Prompt: Implement Event-Based Communication
|
||||||
|
|
||||||
|
```
|
||||||
|
Implement bidirectional event communication in a Tauri 2.0 application.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
Rust side:
|
||||||
|
- Use `use tauri::{Emitter, AppHandle}` to emit events from Rust
|
||||||
|
- Demonstrate both global emit (`app.emit()`) and targeted emit (`app.emit_to()`)
|
||||||
|
- If listening for events on the Rust side, use `use tauri::Listener` and `app.listen()` or `app.once()`
|
||||||
|
|
||||||
|
Frontend side:
|
||||||
|
- Import from `@tauri-apps/api/event` (NOT `@tauri-apps/api/tauri`)
|
||||||
|
- Show `listen()`, `once()`, `emit()`, and `emitTo()` usage
|
||||||
|
- Demonstrate proper cleanup with `unlisten()`
|
||||||
|
- Show targeted listening using `getCurrentWebviewWindow().listen()`
|
||||||
|
|
||||||
|
Show complete code for:
|
||||||
|
1. A Rust command that emits a progress event
|
||||||
|
2. A Rust command that emits a completion event to a specific window
|
||||||
|
3. Frontend listeners that handle these events
|
||||||
|
4. Frontend emitting an event back to Rust
|
||||||
|
|
||||||
|
Make sure `core:event:default` (or specific `core:event:allow-*` permissions) is included in capabilities.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Prompt: Create Multi-Window App
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a Tauri 2.0 application with multiple windows.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Main window defined in `tauri.conf.json` with label "main"
|
||||||
|
2. A command `open_secondary` that creates a new window dynamically using `WebviewWindowBuilder`
|
||||||
|
3. The secondary window should load a local HTML file (`WebviewUrl::App("secondary.html".into())`)
|
||||||
|
4. If creating the window from an async context, use `app_handle.run_on_main_thread()`
|
||||||
|
5. Check for existing windows with the same label using `app_handle.get_webview_window(label)` and focus it if it exists
|
||||||
|
6. Show frontend code for creating windows using `new WebviewWindow(label, options)` from `@tauri-apps/api/webviewWindow`
|
||||||
|
|
||||||
|
CRITICAL: Use `tauri::WebviewWindowBuilder` and `tauri::WebviewUrl` (NOT the v1 `WindowBuilder`/`WindowUrl`).
|
||||||
|
CRITICAL: Use `app_handle.get_webview_window()` (NOT `app_handle.get_window()`).
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Continue to**: [Part 2: Advanced Prompts, Checklist & Migration Map](./02-advanced-prompts-checklist-migration.md)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# Tauri 2.0 Agent Prompts — Definitive Collection
|
# Tauri 2.0 Agent Prompts — Part 2: Advanced Prompts, Checklist & Migration Map
|
||||||
|
|
||||||
> **Purpose**: Copy-paste ready prompts for instructing AI agents to build Tauri 2.0 applications.
|
> **Purpose**: Copy-paste ready prompts for instructing AI agents to build Tauri 2.0 applications.
|
||||||
> **Constraint**: These prompts are designed for sandbox environments where code compilation is NOT available. Agents must verify correctness using logic, API references, and pattern matching.
|
> **Constraint**: These prompts are designed for sandbox environments where code compilation is NOT available. Agents must verify correctness using logic, API references, and pattern matching.
|
||||||
@ -7,13 +7,6 @@
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
1. [Universal System Prompt (Required Prefix)](#1-universal-system-prompt-required-prefix)
|
|
||||||
2. [Prompt: Scaffold a New Tauri 2.0 Project](#2-prompt-scaffold-a-new-tauri-20-project)
|
|
||||||
3. [Prompt: Add a Tauri Plugin](#3-prompt-add-a-tauri-plugin)
|
|
||||||
4. [Prompt: Create a Custom Command (IPC)](#4-prompt-create-a-custom-command-ipc)
|
|
||||||
5. [Prompt: Add State Management](#5-prompt-add-state-management)
|
|
||||||
6. [Prompt: Implement Event-Based Communication](#6-prompt-implement-event-based-communication)
|
|
||||||
7. [Prompt: Create Multi-Window App](#7-prompt-create-multi-window-app)
|
|
||||||
8. [Prompt: Build a WebSocket Server in Rust](#8-prompt-build-a-websocket-server-in-rust)
|
8. [Prompt: Build a WebSocket Server in Rust](#8-prompt-build-a-websocket-server-in-rust)
|
||||||
9. [Prompt: Implement Script Injection](#9-prompt-implement-script-injection)
|
9. [Prompt: Implement Script Injection](#9-prompt-implement-script-injection)
|
||||||
10. [Prompt: Set Up Capabilities & Permissions](#10-prompt-set-up-capabilities--permissions)
|
10. [Prompt: Set Up Capabilities & Permissions](#10-prompt-set-up-capabilities--permissions)
|
||||||
@ -23,200 +16,7 @@
|
|||||||
14. [Quick Reference: v1 vs v2 API Map](#14-quick-reference-v1-vs-v2-api-map)
|
14. [Quick Reference: v1 vs v2 API Map](#14-quick-reference-v1-vs-v2-api-map)
|
||||||
15. [Prompt: Debug a Tauri 2.0 Build/Compile Error](#15-prompt-debug-a-tauri-20-buildcompile-error)
|
15. [Prompt: Debug a Tauri 2.0 Build/Compile Error](#15-prompt-debug-a-tauri-20-buildcompile-error)
|
||||||
|
|
||||||
---
|
**See also**: [Part 1: System Prompt & Common Task Prompts](./01-system-prompt-and-common-prompts.md)
|
||||||
|
|
||||||
## 1. Universal System Prompt (Required Prefix)
|
|
||||||
|
|
||||||
> **Always include this as the system context before any task-specific prompt.**
|
|
||||||
|
|
||||||
```
|
|
||||||
You are a Tauri 2.0 expert developer. You build desktop and mobile applications using the Tauri 2.0 framework (Rust backend + web frontend).
|
|
||||||
|
|
||||||
CRITICAL ENVIRONMENT CONSTRAINTS:
|
|
||||||
- You CANNOT compile or run code in this sandbox. Do NOT attempt compilation.
|
|
||||||
- Verify correctness using logic, API pattern matching, and reference knowledge only.
|
|
||||||
- Cross-reference every code snippet against known Tauri 2.0 API patterns before outputting.
|
|
||||||
- If you are uncertain about an API or pattern, say so explicitly rather than guessing.
|
|
||||||
|
|
||||||
MANDATORY TAURI 2.0 RULES — NEVER VIOLATE:
|
|
||||||
1. This is Tauri 2.0 (NOT v1). The APIs are fundamentally different.
|
|
||||||
2. Use `tauri::WebviewWindow` (NOT `tauri::Window`).
|
|
||||||
3. Use `tauri::WebviewWindowBuilder` (NOT `tauri::WindowBuilder`).
|
|
||||||
4. Use `tauri::WebviewUrl` (NOT `tauri::WindowUrl`).
|
|
||||||
5. Use `Manager::get_webview_window()` (NOT `Manager::get_window()`).
|
|
||||||
6. Use `tauri::Emitter` trait for `emit()` and `emit_to()` (NOT the old window-only emit).
|
|
||||||
7. Use `tauri::Listener` trait for `listen()` and `once()` on the Rust side.
|
|
||||||
8. JS import `invoke` from `@tauri-apps/api/core` (NOT `@tauri-apps/api/tauri`).
|
|
||||||
9. JS import window utilities from `@tauri-apps/api/webviewWindow` (NOT `@tauri-apps/api/window`).
|
|
||||||
10. JS plugins import from `@tauri-apps/plugin-<name>` (NOT `@tauri-apps/api/<name>`).
|
|
||||||
11. Config uses `app` section (NOT `tauri` section), `build.frontendDist` (NOT `build.distDir`).
|
|
||||||
12. Security uses Capabilities + Permissions (NOT `allowlist`).
|
|
||||||
13. Every plugin requires: (a) Rust crate in Cargo.toml, (b) `.plugin()` registration in Builder, (c) npm package, (d) permissions in capabilities.
|
|
||||||
14. Use the `lib.rs` + `main.rs` pattern. Core logic goes in `lib.rs`. `main.rs` only calls `lib::run()`.
|
|
||||||
15. `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is REQUIRED on the `pub fn run()` function.
|
|
||||||
16. `invoke_handler` can only be called ONCE — all commands go in a single `tauri::generate_handler![]`.
|
|
||||||
17. Async commands cannot use borrowed references (`&str`, `&Path`) directly — use owned types or wrap return in `Result`.
|
|
||||||
18. Window creation from async contexts MUST use `app_handle.run_on_main_thread()`.
|
|
||||||
19. Always clean up event listeners (call `unlisten()`) on the frontend.
|
|
||||||
20. Use `tauri = { version = "2" }` and `tauri-build = { version = "2" }` (NOT version 1).
|
|
||||||
|
|
||||||
FILE STRUCTURE:
|
|
||||||
- Frontend source: `src/`
|
|
||||||
- Rust backend: `src-tauri/`
|
|
||||||
- Rust entry-point: `src-tauri/src/main.rs` (minimal)
|
|
||||||
- Core app logic: `src-tauri/src/lib.rs`
|
|
||||||
- Extra modules: `src-tauri/src/<module>.rs`
|
|
||||||
- Capabilities: `src-tauri/capabilities/default.json`
|
|
||||||
- Config: `src-tauri/tauri.conf.json`
|
|
||||||
- Rust deps: `src-tauri/Cargo.toml`
|
|
||||||
- Build script: `src-tauri/build.rs`
|
|
||||||
|
|
||||||
OFFICIAL DOCUMENTATION:
|
|
||||||
- Main docs: https://v2.tauri.app
|
|
||||||
- Rust API: https://docs.rs/tauri/latest/tauri/
|
|
||||||
- Migration guide: https://v2.tauri.app/start/migrate/from-tauri-1/
|
|
||||||
- Capabilities: https://v2.tauri.app/security/capabilities/
|
|
||||||
- Permissions: https://v2.tauri.app/security/permissions/
|
|
||||||
- Configuration: https://v2.tauri.app/develop/configuration-files/
|
|
||||||
- Calling Rust: https://v2.tauri.app/develop/calling-rust/
|
|
||||||
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
|
|
||||||
- Plugin index: https://v2.tauri.app/plugin/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Prompt: Scaffold a New Tauri 2.0 Project
|
|
||||||
|
|
||||||
```
|
|
||||||
Create a new Tauri 2.0 project from scratch with vanilla JavaScript (no frontend framework).
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- App name: [APP_NAME]
|
|
||||||
- Window title: [WINDOW_TITLE]
|
|
||||||
- Default window size: [WIDTH]x[HEIGHT]
|
|
||||||
- withGlobalTauri enabled (for window.__TAURI__ access without npm package)
|
|
||||||
|
|
||||||
Generate ALL of the following files with complete, correct Tauri 2.0 code:
|
|
||||||
|
|
||||||
1. `package.json` — with @tauri-apps/cli v2 devDependency and scripts (dev, build, tauri)
|
|
||||||
2. `src/index.html` — basic HTML5 boilerplate
|
|
||||||
3. `src/main.js` — demonstrate invoking a Rust command
|
|
||||||
4. `src/styles.css` — basic styling
|
|
||||||
5. `src-tauri/tauri.conf.json` — correct v2 structure (app section, build section, bundle section)
|
|
||||||
6. `src-tauri/Cargo.toml` — with tauri v2, tauri-build v2, serde, serde_json. Include [lib] with crate-type ["staticlib", "cdylib", "rlib"]
|
|
||||||
7. `src-tauri/build.rs` — standard tauri_build::build()
|
|
||||||
8. `src-tauri/capabilities/default.json` — with core:default permission
|
|
||||||
9. `src-tauri/src/main.rs` — minimal entry-point calling lib::run()
|
|
||||||
10. `src-tauri/src/lib.rs` — with Builder pattern, mobile_entry_point attribute, a sample "greet" command, and the command registered in invoke_handler
|
|
||||||
|
|
||||||
Verify every line follows Tauri 2.0 conventions. Cross-check config structure against the v2 schema.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Prompt: Add a Tauri Plugin
|
|
||||||
|
|
||||||
```
|
|
||||||
Add the [PLUGIN_NAME] plugin to an existing Tauri 2.0 project.
|
|
||||||
|
|
||||||
Plugin to add: [tauri-plugin-STORE/turi-plugin-FS/etc.]
|
|
||||||
JS package name: [@tauri-apps/plugin-store/etc.]
|
|
||||||
|
|
||||||
Instructions:
|
|
||||||
1. Add the Rust crate to `src-tauri/Cargo.toml` as `tauri-plugin-[name] = "2"`
|
|
||||||
2. Register the plugin in `src-tauri/src/lib.rs` using `.plugin(tauri_plugin_[name]::init())` or the Builder pattern if the plugin requires configuration
|
|
||||||
3. Add the npm package: `@tauri-apps/plugin-[name]`
|
|
||||||
4. Update `src-tauri/capabilities/default.json` to add the appropriate permission (usually `[name]:default` or specific allow permissions)
|
|
||||||
5. Provide a frontend usage example showing the correct v2 import from `@tauri-apps/plugin-[name]`
|
|
||||||
|
|
||||||
Show the exact changes needed for each file. Do NOT use v1 import paths or API patterns.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Prompt: Create a Custom Command (IPC)
|
|
||||||
|
|
||||||
```
|
|
||||||
Create a Tauri 2.0 command called "[COMMAND_NAME]" that [DESCRIBE WHAT IT DOES].
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- Define the command using `#[tauri::command]` in a separate module file `src-tauri/src/commands.rs`
|
|
||||||
- The command must be `pub` (required for separate modules)
|
|
||||||
- Include proper error handling with `Result<T, String>` or a custom error type
|
|
||||||
- Register the command in `src-tauri/src/lib.rs` using `mod commands;` and including it in `generate_handler![commands::COMMAND_NAME]`
|
|
||||||
- Provide the frontend JavaScript code to invoke the command using `invoke()` from `@tauri-apps/api/core` or `window.__TAURI__.core`
|
|
||||||
- If the command is async, ensure all arguments are owned types (not borrowed references)
|
|
||||||
|
|
||||||
Show complete, correct code for both the Rust side and the JS side.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Prompt: Add State Management
|
|
||||||
|
|
||||||
```
|
|
||||||
Add shared state management to an existing Tauri 2.0 application.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
1. Define a state struct in `src-tauri/src/lib.rs` using `std::sync::Mutex` for thread safety
|
|
||||||
2. Manage the state in the Builder using `.manage(MyState { ... })`
|
|
||||||
3. Create at least two commands that read and write the managed state using `tauri::State<'_, MyState>`
|
|
||||||
4. Show the frontend code for invoking these commands
|
|
||||||
|
|
||||||
If persistent storage is also needed:
|
|
||||||
- Add `tauri-plugin-store = "2"` to Cargo.toml
|
|
||||||
- Register the plugin
|
|
||||||
- Add `store:default` to capabilities
|
|
||||||
- Show frontend usage with `@tauri-apps/plugin-store`
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Prompt: Implement Event-Based Communication
|
|
||||||
|
|
||||||
```
|
|
||||||
Implement bidirectional event communication in a Tauri 2.0 application.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
|
|
||||||
Rust side:
|
|
||||||
- Use `use tauri::{Emitter, AppHandle}` to emit events from Rust
|
|
||||||
- Demonstrate both global emit (`app.emit()`) and targeted emit (`app.emit_to()`)
|
|
||||||
- If listening for events on the Rust side, use `use tauri::Listener` and `app.listen()` or `app.once()`
|
|
||||||
|
|
||||||
Frontend side:
|
|
||||||
- Import from `@tauri-apps/api/event` (NOT `@tauri-apps/api/tauri`)
|
|
||||||
- Show `listen()`, `once()`, `emit()`, and `emitTo()` usage
|
|
||||||
- Demonstrate proper cleanup with `unlisten()`
|
|
||||||
- Show targeted listening using `getCurrentWebviewWindow().listen()`
|
|
||||||
|
|
||||||
Show complete code for:
|
|
||||||
1. A Rust command that emits a progress event
|
|
||||||
2. A Rust command that emits a completion event to a specific window
|
|
||||||
3. Frontend listeners that handle these events
|
|
||||||
4. Frontend emitting an event back to Rust
|
|
||||||
|
|
||||||
Make sure `core:event:default` (or specific `core:event:allow-*` permissions) is included in capabilities.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Prompt: Create Multi-Window App
|
|
||||||
|
|
||||||
```
|
|
||||||
Create a Tauri 2.0 application with multiple windows.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
1. Main window defined in `tauri.conf.json` with label "main"
|
|
||||||
2. A command `open_secondary` that creates a new window dynamically using `WebviewWindowBuilder`
|
|
||||||
3. The secondary window should load a local HTML file (`WebviewUrl::App("secondary.html".into())`)
|
|
||||||
4. If creating the window from an async context, use `app_handle.run_on_main_thread()`
|
|
||||||
5. Check for existing windows with the same label using `app_handle.get_webview_window(label)` and focus it if it exists
|
|
||||||
6. Show frontend code for creating windows using `new WebviewWindow(label, options)` from `@tauri-apps/api/webviewWindow`
|
|
||||||
|
|
||||||
CRITICAL: Use `tauri::WebviewWindowBuilder` and `tauri::WebviewUrl` (NOT the v1 `WindowBuilder`/`WindowUrl`).
|
|
||||||
CRITICAL: Use `app_handle.get_webview_window()` (NOT `app_handle.get_window()`).
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -573,3 +373,7 @@ Follow this structured diagnosis process:
|
|||||||
|
|
||||||
If the error doesn't match any known category, say so explicitly and provide your best analysis based on the error message, Rust compiler output, and Tauri 2.0 API knowledge.
|
If the error doesn't match any known category, say so explicitly and provide your best analysis based on the error message, Rust compiler output, and Tauri 2.0 API knowledge.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Back to**: [Part 1: System Prompt & Common Task Prompts](./01-system-prompt-and-common-prompts.md)
|
||||||
833
tauri2-guide/02-runtime-platform-mobile-errors.md
Normal file
833
tauri2-guide/02-runtime-platform-mobile-errors.md
Normal file
@ -0,0 +1,833 @@
|
|||||||
|
# Tauri 2.0 Error Resolution Guide — Part 2: Runtime, Platform & Mobile Errors
|
||||||
|
|
||||||
|
> **Version**: 2.0 | **Last Updated**: 2026-05-31
|
||||||
|
> **Official Docs**: https://v2.tauri.app
|
||||||
|
>
|
||||||
|
> This file covers Categories I-R and the Quick-Reference (runtime, platform-specific, and mobile errors). Part 1 covers build, config and plugin errors.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
9. [Category I: Event System Errors](#category-i-event-system-errors)
|
||||||
|
10. [Category J: Content Security Policy (CSP) Errors](#category-j-content-security-policy-csp-errors)
|
||||||
|
11. [Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)](#category-k-frontend-integration-errors-vite--nextjs--sveltekit)
|
||||||
|
12. [Category L: WebSocket Errors](#category-l-websocket-errors)
|
||||||
|
13. [Category M: Path & Asset Protocol Errors](#category-m-path--asset-protocol-errors)
|
||||||
|
14. [Category N: Shell & Sidecar Errors](#category-n-shell--sidecar-errors)
|
||||||
|
15. [Category O: Mobile Build Errors (iOS & Android)](#category-o-mobile-build-errors-ios--android)
|
||||||
|
16. [Category P: Platform-Specific Build Errors](#category-p-platform-specific-build-errors)
|
||||||
|
17. [Category Q: Panics & Runtime Crashes](#category-q-panics--runtime-crashes)
|
||||||
|
18. [Category R: Error Handling Best Practices](#category-r-error-handling-best-practices)
|
||||||
|
19. [Quick-Reference: Top 25 Most Common Errors](#quick-reference-top-25-most-common-errors)
|
||||||
|
|
||||||
|
**See also**: [Part 1: Build, Config & Plugin Errors](./01-build-config-permission-plugin-errors.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category I: Event System Errors
|
||||||
|
|
||||||
|
### I1. Events Emitted But Not Received by New Window
|
||||||
|
|
||||||
|
**Error:** Events emitted to a newly created window are not received the first time.
|
||||||
|
|
||||||
|
**Cause:** Race condition — the event is emitted before the listener is attached in the new window.
|
||||||
|
|
||||||
|
**Solution:** Use a handshake pattern:
|
||||||
|
```javascript
|
||||||
|
// In new window — announce ready
|
||||||
|
import { emit } from '@tauri-apps/api/event';
|
||||||
|
emit('window-ready', { label: 'my-window' });
|
||||||
|
|
||||||
|
// In main window — respond after ready signal
|
||||||
|
listen('window-ready', (event) => {
|
||||||
|
emitTo(event.payload.label, 'initial-data', myData);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I2. Events Not Working Between Windows
|
||||||
|
|
||||||
|
**Error:** Events emitted from one window are not received by another window.
|
||||||
|
|
||||||
|
**Cause:** Using `emit()` (which broadcasts to all) vs. `emitTo()` (which targets a specific window) incorrectly, or listeners scoped incorrectly.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```rust
|
||||||
|
// Emit to ALL windows
|
||||||
|
app.emit("my-event", payload)?;
|
||||||
|
|
||||||
|
// Emit to a SPECIFIC window
|
||||||
|
app.emit_to("settings-panel", "my-event", payload)?;
|
||||||
|
```
|
||||||
|
```javascript
|
||||||
|
// Frontend: listen globally
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
|
const unlisten = await listen('my-event', handler);
|
||||||
|
|
||||||
|
// Frontend: listen on specific window only
|
||||||
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
const appWebview = getCurrentWebviewWindow();
|
||||||
|
const unlisten2 = await appWebview.listen('my-event', handler);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### I3. Rapid Event Emission Causes Panic
|
||||||
|
|
||||||
|
**Error:** App crashes when emitting large numbers of events rapidly.
|
||||||
|
|
||||||
|
**Cause:** Internal buffer overflow or deadlock in the event system.
|
||||||
|
|
||||||
|
**Solution:** Throttle/debounce event emission. For high-frequency data, use `Channel<T>` instead of events:
|
||||||
|
```rust
|
||||||
|
use tauri::ipc::Channel;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn stream_data(on_event: Channel<MyEvent>) {
|
||||||
|
for item in data_source {
|
||||||
|
on_event.send(item).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category J: Content Security Policy (CSP) Errors
|
||||||
|
|
||||||
|
### J1. `Refused to connect to 'http://ipc.localhost'`
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Refused to connect to 'http://ipc.localhost/test' because it violates the document's Content Security Policy.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** External URLs loaded in webviews have their own CSP that doesn't allow connections to Tauri's IPC endpoint.
|
||||||
|
|
||||||
|
**Solution:** Configure CSP to include IPC:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"security": {
|
||||||
|
"csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://your-url.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
For maximum compatibility with injected scripts on external pages: `"csp": null` (use with caution).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### J2. CSS-in-JS Libraries Blocked by CSP
|
||||||
|
|
||||||
|
**Error:** Styles/scripts from styled-components, emotion, or other CSS-in-JS libraries are blocked.
|
||||||
|
|
||||||
|
**Cause:** Default CSP doesn't allow inline styles.
|
||||||
|
|
||||||
|
**Solution:** Add `'unsafe-inline'` to `style-src`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"security": {
|
||||||
|
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category K: Frontend Integration Errors (Vite / Next.js / SvelteKit)
|
||||||
|
|
||||||
|
### K1. Vite Dev Server Not Connecting / White Screen
|
||||||
|
|
||||||
|
**Error:** Tauri window opens but shows blank white screen. Dev server connection not established.
|
||||||
|
|
||||||
|
**Cause:** Port mismatch between `devUrl` in `tauri.conf.json` and Vite's actual port.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Ensure `devUrl` matches Vite's actual port: `"devUrl": "http://localhost:5173"`
|
||||||
|
2. For mobile, use `process.env.TAURI_DEV_HOST` as the host in Vite config
|
||||||
|
3. Set `server.host` in `vite.config.js` to allow network access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### K2. Vite Watches `src-tauri` — Infinite Recompilation
|
||||||
|
|
||||||
|
**Error:** App keeps recompiling Rust code repeatedly in an infinite loop.
|
||||||
|
|
||||||
|
**Cause:** Vite's file watcher detects changes in `src-tauri/` (from Cargo build output) and triggers re-runs.
|
||||||
|
|
||||||
|
**Solution:** Add `src-tauri` to Vite's ignore list:
|
||||||
|
```javascript
|
||||||
|
// vite.config.js
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
watch: {
|
||||||
|
ignored: ["**/src-tauri/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### K3. Next.js App Router Breaks in Production
|
||||||
|
|
||||||
|
**Error:** `useEffect` hooks broken, syntax errors in chunk files, component rendering failures in production.
|
||||||
|
|
||||||
|
**Cause:** Next.js App Router uses React Server Components and advanced chunking that conflicts with Tauri's custom protocol.
|
||||||
|
|
||||||
|
**Solution:** Use static export only:
|
||||||
|
```javascript
|
||||||
|
// next.config.js
|
||||||
|
module.exports = {
|
||||||
|
output: 'export', // Required — no SSR
|
||||||
|
images: { unoptimized: true }, // No server-side image optimization
|
||||||
|
};
|
||||||
|
```
|
||||||
|
Add `'use client'` directives to all interactive components.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### K4. Next.js `assetPrefix` Breaks Hot Reload
|
||||||
|
|
||||||
|
**Error:** Hot reload doesn't work when `assetPrefix` is set for Tauri.
|
||||||
|
|
||||||
|
**Cause:** `assetPrefix` changes the URL for WebSocket connections, breaking HMR.
|
||||||
|
|
||||||
|
**Solution:** Only set `assetPrefix` in production:
|
||||||
|
```javascript
|
||||||
|
// next.config.js
|
||||||
|
module.exports = {
|
||||||
|
output: 'export',
|
||||||
|
assetPrefix: process.env.NODE_ENV === 'production' ? './' : undefined,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### K5. Next.js Standalone Output — `asset not found: index.html`
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
asset not found: index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Next.js standalone output doesn't produce a static `index.html` file.
|
||||||
|
|
||||||
|
**Solution:** Use `output: "export"`, NOT `standalone`:
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
output: 'export', // Required for Tauri
|
||||||
|
// NOT output: 'standalone'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### K6. SvelteKit Blank Screen / PostCSS Errors
|
||||||
|
|
||||||
|
**Error:** Blank white screen or `[postcss] Internal server error`.
|
||||||
|
|
||||||
|
**Cause:** SvelteKit's routing conflicts with Tauri's static file serving.
|
||||||
|
|
||||||
|
**Solution:** Use `@sveltejs/adapter-static`:
|
||||||
|
```javascript
|
||||||
|
// svelte.config.js
|
||||||
|
import adapter from '@sveltejs/adapter-static';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
kit: {
|
||||||
|
adapter: adapter({ fallback: 'index.html' })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
Add to root layout: `export const ssr = false;`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category L: WebSocket Errors
|
||||||
|
|
||||||
|
### L1. WebSocket Connection Closing / "Broken Pipe"
|
||||||
|
|
||||||
|
**Error:** WebSocket connection closes unexpectedly after app has been open for a long time.
|
||||||
|
|
||||||
|
**Cause:** Idle timeouts or OS-level connection cleanup.
|
||||||
|
|
||||||
|
**Solution:** Implement ping/pong keepalive in custom WebSocket connections:
|
||||||
|
```javascript
|
||||||
|
const ws = new WebSocket('ws://127.0.0.1:8080');
|
||||||
|
|
||||||
|
// Keepalive
|
||||||
|
setInterval(() => {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(JSON.stringify({ type: 'ping' }));
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### L2. WebSocket Plugin — Unstable Connections
|
||||||
|
|
||||||
|
**Error:** Messages not received right after connection with `tauri-plugin-websocket`.
|
||||||
|
|
||||||
|
**Cause:** The WebSocket connection may not be fully initialized before messages are sent.
|
||||||
|
|
||||||
|
**Solution:** Wait for the `onopen` event:
|
||||||
|
```javascript
|
||||||
|
import WebSocket from '@tauri-apps/plugin-websocket';
|
||||||
|
|
||||||
|
const ws = await WebSocket.connect('ws://localhost:8080');
|
||||||
|
// Ensure ready before sending
|
||||||
|
ws.addListener((msg) => {
|
||||||
|
console.log('Received:', msg);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### L3. WebSocket + Tauri Client Conflict
|
||||||
|
|
||||||
|
**Error:** WebSocket client code and Tauri app cannot run simultaneously.
|
||||||
|
|
||||||
|
**Cause:** Resource conflicts between the Tauri WebView and WebSocket connections, especially on mobile.
|
||||||
|
|
||||||
|
**Solution:** Use `tauri-plugin-websocket` instead of raw `WebSocket` API for mobile compatibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category M: Path & Asset Protocol Errors
|
||||||
|
|
||||||
|
### M1. 403 Forbidden on Asset Protocol URLs
|
||||||
|
|
||||||
|
**Error:** `403 Forbidden` when accessing files via `asset://` protocol.
|
||||||
|
|
||||||
|
**Cause:** The asset protocol has a scope restricting which directories can be accessed.
|
||||||
|
|
||||||
|
**Solution:** Configure asset protocol scope:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"identifier": "asset-capability",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"identifier": "fs:allow-read",
|
||||||
|
"allow": [{ "path": "$RESOURCE/**" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Use `convertFileSrc()` from `@tauri-apps/api/core` for proper path conversion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### M2. `resource_dir()` Returns Unknown Path
|
||||||
|
|
||||||
|
**Error:** `PathResolver.resource_dir()` returns empty or unknown path.
|
||||||
|
|
||||||
|
**Cause:** A custom `target-dir` in `~/.cargo/config.toml` breaks resource resolution logic.
|
||||||
|
|
||||||
|
**Solution:** Remove global `target-dir` setting, or configure it per-project instead of globally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### M3. Path Traversal Prevention Blocking Valid Paths
|
||||||
|
|
||||||
|
**Error:** `path traversal` error from `@tauri-apps/plugin-fs`.
|
||||||
|
|
||||||
|
**Cause:** The FS plugin blocks paths containing `..`.
|
||||||
|
|
||||||
|
**Solution:** Normalize paths before use:
|
||||||
|
```javascript
|
||||||
|
import { resolve } from '@tauri-apps/api/path';
|
||||||
|
const normalizedPath = await resolve('some', 'relative', 'path');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category N: Shell & Sidecar Errors
|
||||||
|
|
||||||
|
### N1. Sidecar Binary Not Found
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Failed to spawn my-binary: Io(Os { code: 3, kind: NotFound })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** The sidecar binary doesn't follow Tauri's naming convention. Tauri appends the target triple to the binary name.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```json
|
||||||
|
// tauri.conf.json
|
||||||
|
{
|
||||||
|
"bundle": {
|
||||||
|
"externalBin": ["binaries/my-binary"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Name the binary with the target triple suffix:
|
||||||
|
- Windows: `my-binary-x86_64-pc-windows-msvc.exe`
|
||||||
|
- macOS: `my-binary-aarch64-apple-darwin`
|
||||||
|
- Linux: `my-binary-x86_64-unknown-linux-gnu`
|
||||||
|
|
||||||
|
Reference in Rust without the suffix:
|
||||||
|
```rust
|
||||||
|
app.shell().sidecar("binaries/my-binary").unwrap()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category O: Mobile Build Errors (iOS & Android)
|
||||||
|
|
||||||
|
### O1. `xcodebuild exited with code 65`
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
error: failed to run custom build command for tauri v2 ... xcodebuild exited with code 65
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** iOS build failure. Common causes: code signing issues, stale build artifacts, Xcode version incompatibility.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Open Xcode project: `open src-tauri/gen/apple/Project.xcworkspace`
|
||||||
|
2. Set Signing & Capabilities → Team
|
||||||
|
3. Clean build: `Cmd+Shift+K` in Xcode
|
||||||
|
4. Clean pods: `cd src-tauri/gen/apple && rm -rf build Pods && pod install`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O2. `can't find crate for core` (Missing Mobile Target)
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
error[E0463]: can't find crate for `core`
|
||||||
|
```
|
||||||
|
When building for iOS or Android.
|
||||||
|
|
||||||
|
**Cause:** The Rust target for the mobile platform is not installed.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# iOS
|
||||||
|
rustup target add aarch64-apple-ios
|
||||||
|
|
||||||
|
# Android (all targets)
|
||||||
|
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
|
||||||
|
cargo install cargo-ndk
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O3. `building for 'iOS', but linking in object file built for 'macOS'`
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
building for 'iOS', but linking in object file built for 'macOS'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** A native dependency was compiled for the host platform instead of the iOS target.
|
||||||
|
|
||||||
|
**Solution:** Check which dependency causes the issue: `cargo build --target aarch64-apple-ios -v`. Ensure all dependencies support cross-compilation to iOS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O4. Android Build Fails — `Cannot find module 'tauri'`
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Cannot find module 'tauri' during Gradle build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Node.js executable can't be found when Gradle's BuildTask runs.
|
||||||
|
|
||||||
|
**Solution:** Ensure Node.js is available system-wide. Use `npx tauri` or configure the Gradle BuildTask to use the correct node path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O5. Android App Crashes on Launch
|
||||||
|
|
||||||
|
**Error:** App immediately crashes after deployment to Android device/emulator.
|
||||||
|
|
||||||
|
**Cause:** Missing Android SDK components, incorrect NDK configuration, or incompatible Gradle/AGP versions.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Ensure correct Android SDK and NDK are installed
|
||||||
|
2. Run `npx tauri android init` to set up properly
|
||||||
|
3. Check `android/app/build.gradle` has correct Tauri integration
|
||||||
|
4. Verify ADB connection: `adb devices`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O6. `tauri android dev` Hangs Indefinitely
|
||||||
|
|
||||||
|
**Error:** Command hangs with no output or progress.
|
||||||
|
|
||||||
|
**Cause:** Emulator/device connection failure or Gradle build hanging.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Verify ADB: `adb devices`
|
||||||
|
2. Clean Gradle cache: `cd android && ./gradlew clean`
|
||||||
|
3. Use a physical device instead of emulator
|
||||||
|
4. Increase Gradle memory in `android/gradle.properties`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O7. `mobile_entry_point` Conflicts with `#[tokio::main]`
|
||||||
|
|
||||||
|
**Error:** App crashes on iOS when using `#[tokio::main]` with `mobile_entry_point`.
|
||||||
|
|
||||||
|
**Cause:** `mobile_entry_point` and `#[tokio::main]` generate conflicting function signatures.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```rust
|
||||||
|
// ❌ WRONG
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn run() { /* ... */ }
|
||||||
|
|
||||||
|
// ✅ CORRECT — use async_runtime::block_on
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::async_runtime::block_on(async {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O8. No Code Signing Certificates Found (iOS)
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
No code signing certificates found. You must add one and set the certificate development team ID.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** No Apple Developer certificate configured.
|
||||||
|
|
||||||
|
**Solution:** Open `src-tauri/gen/apple/Project.xcworkspace` in Xcode → Signing & Capabilities → select Team. Or use environment variables: `APPLE_SIGNING_IDENTITY`, `APPLE_TEAM_ID`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O9. Android SDK Not Found
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Android SDK not found at ANDROID_HOME. Please set the ANDROID_HOME environment variable.
|
||||||
|
```
|
||||||
|
Or Gradle fails with `SDK location not found`.
|
||||||
|
|
||||||
|
**Cause:** The `ANDROID_HOME` (or `ANDROID_SDK_ROOT`) environment variable is not set, or points to a nonexistent directory. Tauri's Android build requires the Android SDK to be available.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Install Android Studio, which bundles the SDK
|
||||||
|
2. Set the environment variable:
|
||||||
|
```bash
|
||||||
|
# macOS/Linux — add to ~/.zshrc or ~/.bashrc
|
||||||
|
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||||
|
export PATH="$ANDROID_HOME/platform-tools:$PATH"
|
||||||
|
|
||||||
|
# Windows — set via System Properties → Environment Variables
|
||||||
|
ANDROID_HOME=C:\Users\<you>\AppData\Local\Android\Sdk
|
||||||
|
```
|
||||||
|
3. Verify: `echo $ANDROID_HOME` and `adb version`
|
||||||
|
4. Re-run `npx tauri android init`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O10. iOS Development Requires macOS
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
iOS is not supported on this platform
|
||||||
|
```
|
||||||
|
Or `xcodebuild: command not found` when running on Linux or Windows.
|
||||||
|
|
||||||
|
**Cause:** iOS builds require Xcode and the Apple toolchain, which are only available on macOS. Cross-compilation to iOS from Linux or Windows is not supported by Tauri.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Use a Mac (physical or CI runner like GitHub Actions `macos-latest`)
|
||||||
|
2. Install Xcode from the Mac App Store
|
||||||
|
3. Accept the Xcode license: `sudo xcodebuild -license accept`
|
||||||
|
4. Install CocoaPods: `sudo gem install cocoapods` (or `brew install cocoapods`)
|
||||||
|
5. For CI, use GitHub Actions with `macos-latest` runner
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### O11. `pod install` Failed (iOS)
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Pod installation failed. CocoaPods could not find compatible versions for pod "TauriDriver"
|
||||||
|
```
|
||||||
|
Or `pod: command not found`.
|
||||||
|
|
||||||
|
**Cause:** CocoaPods is not installed, outdated, or the Podfile.lock is stale and conflicts with updated dependencies.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Ensure CocoaPods is installed:
|
||||||
|
```bash
|
||||||
|
sudo gem install cocoapods
|
||||||
|
# Or via Homebrew:
|
||||||
|
brew install cocoapods
|
||||||
|
```
|
||||||
|
2. Clean and reinstall pods:
|
||||||
|
```bash
|
||||||
|
cd src-tauri/gen/apple
|
||||||
|
rm -rf Podfile.lock Pods
|
||||||
|
pod install --repo-update
|
||||||
|
```
|
||||||
|
3. If version conflicts persist, update CocoaPods to the latest version: `sudo gem install cocoapods --pre`
|
||||||
|
4. Ensure your Xcode Command Line Tools are up to date: `xcode-select --install`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category P: Platform-Specific Build Errors
|
||||||
|
|
||||||
|
### P1. Linux — Missing System Dependencies
|
||||||
|
|
||||||
|
**Error:** Build fails with GTK/WebKit-related errors.
|
||||||
|
|
||||||
|
**Cause:** Missing system libraries for GTK/WebKit development.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev \
|
||||||
|
librsvg2-dev libsoup-3.0-dev
|
||||||
|
|
||||||
|
# Fedora
|
||||||
|
sudo dnf install webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel \
|
||||||
|
librsvg2-devel libsoup3-devel
|
||||||
|
|
||||||
|
# Arch
|
||||||
|
sudo pacman -S webkit2gtk-4.1 gtk3 libappindicator-gtk3 librsvg libsoup3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P2. Linux — Failed to Find ICNS Encoder
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
error: failed to compile tauri-bundler: failed to find ICNS encoder
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Missing icon tools for macOS bundle creation on Linux.
|
||||||
|
|
||||||
|
**Solution:** `sudo apt install icnsutils` (Debian/Ubuntu) or equivalent package.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P3. Linux — Failed to Run `linuxdeploy`
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
error: failed to bundle project: failed to run linuxdeploy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Missing AppImage bundling dependencies.
|
||||||
|
|
||||||
|
**Solution:** Install `linuxdeploy` and `linuxdeploy-plugin-gtk`. Ensure `appimage-builder` is available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category Q: Panics & Runtime Crashes
|
||||||
|
|
||||||
|
### Q1. Generic "error while running tauri application"
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
thread 'main' panicked at src\main.rs:14:10:
|
||||||
|
error while running tauri application: <inner error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Top-level error wrapper. The inner error reveals the actual problem — check plugin initialization, WebView creation, or config validation.
|
||||||
|
|
||||||
|
**Solution:** Replace `.expect()` with better error reporting:
|
||||||
|
```rust
|
||||||
|
if let Err(e) = tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_http::init())
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
{
|
||||||
|
eprintln!("Failed to start: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q2. Panic in tauri-runtime-wry on Wayland
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Panic in tauri-runtime-wry during init on Wayland
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Wayland compositors missing `wlr_data_control_manager v1` protocol cause the clipboard crate to panic.
|
||||||
|
|
||||||
|
**Solution:** Use XWayland, or use a Wayland compositor that supports the protocol.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q3. `EXC_BAD_ACCESS` Crash on iOS
|
||||||
|
|
||||||
|
**Error:** App crashes on physical iPhone with `EXC_BAD_ACCESS` but works in simulator.
|
||||||
|
|
||||||
|
**Cause:** Memory access violations in native code bridging.
|
||||||
|
|
||||||
|
**Solution:** Enable address sanitizer in Xcode. Check for use-after-free or null pointer issues in Rust FFI code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q4. Panic When `Builder::setup` Hook Fails
|
||||||
|
|
||||||
|
**Error:** App panics immediately when the `setup` callback returns an error.
|
||||||
|
|
||||||
|
**Cause:** `App::run()` panics when setup fails — no graceful error handling.
|
||||||
|
|
||||||
|
**Solution:** Wrap setup logic properly:
|
||||||
|
```rust
|
||||||
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
match critical_init(app) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Setup failed: {e}");
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category R: Error Handling Best Practices
|
||||||
|
|
||||||
|
### R1. Never Panic Inside Tauri Commands
|
||||||
|
|
||||||
|
Panic in a **synchronous** command crashes the app. Panic in an **asynchronous** command causes a Promise that never resolves (silent failure). Always return `Result`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum AppError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Network(#[from] reqwest::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for AppError {
|
||||||
|
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: serde::ser::Serializer {
|
||||||
|
s.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn my_command() -> Result<String, AppError> {
|
||||||
|
let data = std::fs::read_to_string("config.toml")?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### R2. Frontend Error Handling
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await invoke('my_command', { arg: 'value' });
|
||||||
|
console.log('Success:', result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Command failed:', error);
|
||||||
|
// Show user-friendly error in UI
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### R3. Structured Errors for the Frontend
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
#[serde(tag = "kind", content = "message")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
enum ErrorKind {
|
||||||
|
Io(String),
|
||||||
|
Network(String),
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for AppError {
|
||||||
|
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: serde::ser::Serializer {
|
||||||
|
let kind = match self {
|
||||||
|
Self::Io(e) => ErrorKind::Io(e.to_string()),
|
||||||
|
Self::Network(e) => ErrorKind::Network(e.to_string()),
|
||||||
|
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
|
||||||
|
};
|
||||||
|
kind.serialize(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
|
||||||
|
|
||||||
|
### R4. Cleanup Event Listeners
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
|
|
||||||
|
// ❌ WRONG — memory leak
|
||||||
|
useEffect(() => {
|
||||||
|
listen('my-event', handler);
|
||||||
|
return () => {}; // No cleanup!
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ✅ CORRECT — proper cleanup
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen('my-event', handler);
|
||||||
|
return () => { unlisten.then(fn => fn()); };
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick-Reference: Top 25 Most Common Errors
|
||||||
|
|
||||||
|
| # | Error Message | Category | Quick Fix |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `X not allowed. Plugin not found` | Permission | Add permissions to `capabilities/default.json` |
|
||||||
|
| 2 | `Additional properties not allowed ('devPath', 'distDir')` | Config | Rename to `devUrl` / `frontendDist` |
|
||||||
|
| 3 | `__TAURI__ is not defined` | Frontend | Set `withGlobalTauri: true` or use npm imports |
|
||||||
|
| 4 | `AssetNotFound("index.html")` | Config | Fix `frontendDist` path to match bundler output |
|
||||||
|
| 5 | `unresolved import 'tauri::api'` | Migration | Replace with plugins (`tauri-plugin-*`) |
|
||||||
|
| 6 | `cannot find type 'Window' in 'tauri'` | Migration | Use `WebviewWindow` |
|
||||||
|
| 7 | `failed to create webview: WebView2 error` | Webview | Install WebView2; remove custom browser args |
|
||||||
|
| 8 | `Path not allowed on the configured scope` | Permission | Add FS scope permissions |
|
||||||
|
| 9 | `command not found` | IPC | Add command to `generate_handler![]` |
|
||||||
|
| 10 | `cannot find type 'SystemTray'` | Migration | Use `tauri::tray::TrayIconBuilder` |
|
||||||
|
| 11 | `lifetime not constrained` in async | Async | Use owned types (`String`, not `&str`) |
|
||||||
|
| 12 | `MutexGuard cannot be sent between threads` | Async | Use `tokio::sync::Mutex` instead of `std::sync::Mutex` |
|
||||||
|
| 13 | `xcodebuild exited with code 65` | Mobile | Set signing team in Xcode |
|
||||||
|
| 14 | `can't find crate for core` | Mobile | `rustup target add <target>` |
|
||||||
|
| 15 | `Additional properties not allowed ('allowlist')` | Config | Remove; use v2 capabilities system |
|
||||||
|
| 16 | `beforeDevCommand terminated with non-zero status` | Config | Fix npm scripts and port numbers |
|
||||||
|
| 17 | Vite infinite recompilation | Frontend | Add `src-tauri` to `watch.ignored` |
|
||||||
|
| 18 | Next.js `useEffect` broken in production | Frontend | Use `output: "export"` + `'use client'` |
|
||||||
|
| 19 | `program not allowed on shell scope` | Shell | Add program to shell capability scope |
|
||||||
|
| 20 | `Refused to connect to 'http://ipc.localhost'` | CSP | Add `ipc: http://ipc.localhost` to `connect-src` |
|
||||||
|
| 21 | Only last `invoke_handler` works | IPC | Combine all commands in one `generate_handler![]` |
|
||||||
|
| 22 | `GDK may only be used from the main thread` | Platform | Update Tauri; avoid cross-thread UI calls |
|
||||||
|
| 23 | `PluginInitialization("http", "invalid type")` | Config | Migrate old plugins config to v2 capabilities |
|
||||||
|
| 24 | Sidecar `Io(Os { code: 3 })` | Shell | Fix binary naming with target triple suffix |
|
||||||
|
| 25 | `error while running tauri application` | Generic | Check inner error; improve error handling |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Back to**: [Part 1: Build, Config & Plugin Errors](./01-build-config-permission-plugin-errors.md)
|
||||||
@ -1,19 +1,14 @@
|
|||||||
# Tauri 2.0 Definitive Developer Guide
|
# Tauri 2.0 Developer Guide — Part 2: Windows, Plugins, WebSocket, State & Migration
|
||||||
|
|
||||||
> **Version**: 2.0 | **Last Updated**: 2026-05-31
|
> **Version**: 2.0 | **Last Updated**: 2026-05-31
|
||||||
> **Official Docs**: https://v2.tauri.app
|
> **Official Docs**: https://v2.tauri.app
|
||||||
|
>
|
||||||
|
> This file covers Sections 8-15 of the Definitive Guide. Part 1 covers Architecture, Config, Capabilities, Commands & IPC.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
1. [Introduction & Architecture Overview](#1-introduction--architecture-overview)
|
|
||||||
2. [Project Structure & Scaffolding](#2-project-structure--scaffolding)
|
|
||||||
3. [Configuration System (`tauri.conf.json`)](#3-configuration-system-tauriconfjson)
|
|
||||||
4. [Capabilities & Permissions (Security Model)](#4-capabilities--permissions-security-model)
|
|
||||||
5. [Rust Backend: `lib.rs` & Commands](#5-rust-backend-librs--commands)
|
|
||||||
6. [Frontend Integration Patterns](#6-frontend-integration-patterns)
|
|
||||||
7. [IPC: Commands, Events & Channels](#7-ipc-commands-events--channels)
|
|
||||||
8. [Window Management](#8-window-management)
|
8. [Window Management](#8-window-management)
|
||||||
9. [Plugin Ecosystem](#9-plugin-ecosystem)
|
9. [Plugin Ecosystem](#9-plugin-ecosystem)
|
||||||
10. [Native WebSocket Server (Rust-side)](#10-native-websocket-server-rust-side)
|
10. [Native WebSocket Server (Rust-side)](#10-native-websocket-server-rust-side)
|
||||||
@ -23,914 +18,9 @@
|
|||||||
14. [Common Anti-Patterns to Avoid](#14-common-anti-patterns-to-avoid)
|
14. [Common Anti-Patterns to Avoid](#14-common-anti-patterns-to-avoid)
|
||||||
15. [Quick Reference Cheat Sheet](#15-quick-reference-cheat-sheet)
|
15. [Quick Reference Cheat Sheet](#15-quick-reference-cheat-sheet)
|
||||||
|
|
||||||
---
|
**See also**: [Part 1: Core Architecture, Config, Commands & IPC](./01-core-architecture-config-commands-ipc.md)
|
||||||
|
|
||||||
## 1. Introduction & Architecture Overview
|
|
||||||
|
|
||||||
Tauri 2.0 is a framework for building small, fast binaries for all major desktop and mobile platforms. It uses a **hybrid architecture** where a Rust backend handles system-level operations while a web frontend (HTML/CSS/JS, or any framework like React/Vue/Svelte) renders the UI inside a native OS webview.
|
|
||||||
|
|
||||||
### Core Architecture Principles
|
|
||||||
|
|
||||||
- **Rust Core (`src-tauri/`)**: Handles system access, file operations, network calls, window management, and any heavy computation. All system interactions MUST go through Rust to maintain security boundaries.
|
|
||||||
- **Web Frontend (`src/`)**: Renders the UI using standard web technologies. Communicates with Rust exclusively through Tauri's IPC mechanisms (`invoke`, events, channels).
|
|
||||||
- **Security Boundary**: The frontend runs in a sandboxed webview. It cannot directly access the filesystem, network, or OS APIs — it must request these through Tauri commands and plugins, which are governed by the **Capabilities & Permissions** system.
|
|
||||||
- **Mobile Support (NEW in v2)**: Tauri 2.0 adds first-class iOS and Android support. This requires a specific project structure (`lib.rs` + `main.rs` pattern) and platform-specific capability configurations.
|
|
||||||
|
|
||||||
### What Changed from v1 to v2
|
|
||||||
|
|
||||||
Tauri 2.0 is a **complete architectural overhaul**, not an incremental update. The most significant changes are:
|
|
||||||
|
|
||||||
| Area | v1 | v2 |
|
|
||||||
|------|----|----|
|
|
||||||
| Security | `allowlist` in config | **Capabilities + Permissions** (ACL-based) |
|
|
||||||
| APIs | Built-in `tauri::api` module | **Everything is a plugin** |
|
|
||||||
| Window type | `Window` / `WindowBuilder` | **`WebviewWindow` / `WebviewWindowBuilder`** |
|
|
||||||
| Config structure | Nested `tauri >` key | Flattened: `app`, `bundle`, `build` |
|
|
||||||
| Event system | Per-window scoped | **`emit()` is global; `emit_to()` for targeting** |
|
|
||||||
| JS imports | `@tauri-apps/api/tauri` | **`@tauri-apps/api/core`** |
|
|
||||||
| Mobile support | Community plugins | **First-class iOS/Android support** |
|
|
||||||
| Project structure | `main.rs` only | **`lib.rs` + `main.rs` pattern required** |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Project Structure & Scaffolding
|
|
||||||
|
|
||||||
### Standard Tauri 2.0 File Hierarchy
|
|
||||||
|
|
||||||
```text
|
|
||||||
my-app/
|
|
||||||
├── src/ # Frontend UI source
|
|
||||||
│ ├── index.html # Main HTML entry
|
|
||||||
│ ├── main.js # Frontend JavaScript
|
|
||||||
│ └── styles.css # Styles
|
|
||||||
├── src-tauri/ # Rust backend environment
|
|
||||||
│ ├── capabilities/ # Security capability definitions
|
|
||||||
│ │ └── default.json # Maps windows → permissions
|
|
||||||
│ ├── icons/ # App icons for all platforms
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── main.rs # Minimal desktop entry-point
|
|
||||||
│ │ ├── lib.rs # Core application setup & commands
|
|
||||||
│ │ └── <modules>.rs # Additional Rust modules
|
|
||||||
│ ├── build.rs # Tauri build script
|
|
||||||
│ ├── Cargo.toml # Rust dependencies
|
|
||||||
│ ├── Cargo.lock # Lockfile (COMMIT THIS)
|
|
||||||
│ └── tauri.conf.json # Main Tauri configuration
|
|
||||||
├── package.json # Node.js dependencies & scripts
|
|
||||||
└── package-lock.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating a New Project
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Using the interactive CLI scaffolder
|
|
||||||
npm create tauri-app@latest
|
|
||||||
|
|
||||||
# Or with specific template
|
|
||||||
npm create tauri-app@latest -- --template vanilla-ts
|
|
||||||
|
|
||||||
# Cargo alternative
|
|
||||||
cargo create-tauri-app
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported templates: Vanilla, Vue, Svelte, React, Solid, Angular, Preact, Yew, Leptos, Sycamore.
|
|
||||||
|
|
||||||
### Adding Tauri to an Existing Frontend
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -D @tauri-apps/cli@latest
|
|
||||||
npx tauri init
|
|
||||||
```
|
|
||||||
|
|
||||||
The `init` command will prompt for: app name, window title, web assets location, dev server URL, and frontend build/dev commands.
|
|
||||||
|
|
||||||
### The `lib.rs` + `main.rs` Pattern (Required in v2)
|
|
||||||
|
|
||||||
Tauri 2.0 requires your core application logic to live in `lib.rs`, with a minimal `main.rs` that calls into it. This is **mandatory** for mobile support because mobile platforms require a shared library entry-point rather than a standard `main()` function.
|
|
||||||
|
|
||||||
**`src-tauri/src/main.rs`** — Desktop-only entry-point:
|
|
||||||
```rust
|
|
||||||
// Prevents additional console window on Windows in release mode
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
my_app_lib::run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`src-tauri/src/lib.rs`** — Core application:
|
|
||||||
```rust
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
pub fn run() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
// plugins, commands, setup, etc.
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running tauri application");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`src-tauri/Cargo.toml`** — Must declare the library:
|
|
||||||
```toml
|
|
||||||
[lib]
|
|
||||||
name = "my_app_lib"
|
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
||||||
```
|
|
||||||
|
|
||||||
The `_lib` suffix in the crate name prevents naming conflicts with the binary on Windows. The three crate types cover: static linking (mobile), dynamic linking (mobile), and Rust library usage (desktop/tests).
|
|
||||||
|
|
||||||
### Development Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run in development mode (hot-reloads UI + auto-recompiles Rust)
|
|
||||||
npm run tauri dev
|
|
||||||
# or: pnpm tauri dev, bun tauri dev, cargo tauri dev
|
|
||||||
|
|
||||||
# Build production artifacts
|
|
||||||
npm run tauri build
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Configuration System (`tauri.conf.json`)
|
|
||||||
|
|
||||||
### Main Configuration File
|
|
||||||
|
|
||||||
The primary configuration file is `src-tauri/tauri.conf.json`. Tauri 2.0 supports three formats:
|
|
||||||
|
|
||||||
| Format | Feature Flag Required |
|
|
||||||
|--------|-----------------------|
|
|
||||||
| **JSON** (default) | None |
|
|
||||||
| **JSON5** | `config-json5` on both `tauri` and `tauri-build` |
|
|
||||||
| **TOML** | `config-toml` on both `tauri` and `tauri-build` |
|
|
||||||
|
|
||||||
### Complete v2 Configuration Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"$schema": "https://tauri.app",
|
|
||||||
"productName": "my-app",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"identifier": "com.mycompany.myapp",
|
|
||||||
"build": {
|
|
||||||
"frontendDist": "../dist",
|
|
||||||
"devUrl": "http://localhost:1420",
|
|
||||||
"beforeDevCommand": "npm run dev",
|
|
||||||
"beforeBuildCommand": "npm run build"
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"withGlobalTauri": true,
|
|
||||||
"windows": [
|
|
||||||
{
|
|
||||||
"label": "main",
|
|
||||||
"title": "My App",
|
|
||||||
"width": 800,
|
|
||||||
"height": 600,
|
|
||||||
"resizable": true,
|
|
||||||
"fullscreen": false,
|
|
||||||
"center": true,
|
|
||||||
"dragDropEnabled": true,
|
|
||||||
"useHttpsScheme": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
|
||||||
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'",
|
|
||||||
"assetProtocol": {
|
|
||||||
"scope": ["$APPDATA/**", "$RESOURCE/**"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"licenseFile": "LICENSE",
|
|
||||||
"copyright": "",
|
|
||||||
"category": "Utility"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Configuration Changes from v1
|
|
||||||
|
|
||||||
| v1 Path | v2 Path | Notes |
|
|
||||||
|---------|---------|-------|
|
|
||||||
| `package.productName` | Top-level `productName` | Moved out of `package` |
|
|
||||||
| `package.version` | Top-level `version` | Moved out of `package` |
|
|
||||||
| `package` | *(removed)* | Fields redistributed |
|
|
||||||
| `build.distDir` | `build.frontendDist` | Renamed |
|
|
||||||
| `build.devPath` | `build.devUrl` | Renamed |
|
|
||||||
| `build.withGlobalTauri` | `app.withGlobalTauri` | Moved to `app` section |
|
|
||||||
| `tauri.*` | `app.*` | Top-level key renamed |
|
|
||||||
| `tauri.allowlist` | *(removed)* | Replaced by capabilities |
|
|
||||||
| `tauri.windows.fileDropEnabled` | `app.windows.dragDropEnabled` | Renamed |
|
|
||||||
| `tauri.bundle` | Top-level `bundle` | Promoted to top-level |
|
|
||||||
| `tauri.updater` | `plugins.updater` | Moved to plugins |
|
|
||||||
| `tauri.systemTray` | `app.trayIcon` | Renamed |
|
|
||||||
| `tauri.cli` | `plugins.cli` | Moved to plugins |
|
|
||||||
| `bundle.identifier` | Top-level `identifier` | Promoted to top-level |
|
|
||||||
|
|
||||||
### Platform-Specific Configuration
|
|
||||||
|
|
||||||
Create platform override files that **merge** with the main config using JSON Merge Patch (RFC 7396). Arrays are replaced entirely (not element-by-element merged).
|
|
||||||
|
|
||||||
| Platform | File Pattern |
|
|
||||||
|----------|-------------|
|
|
||||||
| Linux | `tauri.linux.conf.json` or `Tauri.linux.toml` |
|
|
||||||
| Windows | `tauri.windows.conf.json` or `Tauri.windows.toml` |
|
|
||||||
| macOS | `tauri.macos.conf.json` or `Tauri.macos.toml` |
|
|
||||||
| Android | `tauri.android.conf.json` or `Tauri.android.toml` |
|
|
||||||
| iOS | `tauri.ios.conf.json` or `Tauri.ios.toml` |
|
|
||||||
|
|
||||||
### Cargo.toml Dependencies
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[build-dependencies]
|
|
||||||
tauri-build = { version = "2", features = [] }
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tauri = { version = "2", features = [] }
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important**: Keep `tauri`, `tauri-build`, and `@tauri-apps/cli` on the same minor version. Always commit `Cargo.lock` for reproducible builds.
|
|
||||||
|
|
||||||
### package.json Scripts
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"tauri": "tauri",
|
|
||||||
"dev": "tauri dev",
|
|
||||||
"build": "tauri build"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tauri-apps/cli": "^2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `"tauri"` script is only required when using `npm` (not needed for yarn/pnpm/bun).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Capabilities & Permissions (Security Model)
|
|
||||||
|
|
||||||
Tauri 2.0 introduces a strict **Access Control List (ACL)** security model that replaces the v1 `allowlist`. This is the most important new concept to understand.
|
|
||||||
|
|
||||||
### Core Concepts
|
|
||||||
|
|
||||||
- **Capabilities** define **which permissions** are granted to **which windows or webviews**.
|
|
||||||
- **Permissions** describe explicit privileges for specific commands (e.g., `fs:allow-read`, `http:allow-request`).
|
|
||||||
- **Scopes** further restrict what a permission can access (e.g., only certain file paths).
|
|
||||||
- Multiple capabilities can apply to the same window — permissions **merge**.
|
|
||||||
- Security boundaries are based on **window labels** (not titles).
|
|
||||||
|
|
||||||
### Capability File Location
|
|
||||||
|
|
||||||
```
|
|
||||||
src-tauri/capabilities/<identifier>.json (or .toml)
|
|
||||||
```
|
|
||||||
|
|
||||||
All files in this directory are **automatically loaded** by default.
|
|
||||||
|
|
||||||
### Basic Capability File
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
|
||||||
"identifier": "main-window-capability",
|
|
||||||
"description": "Permissions for the primary application window",
|
|
||||||
"platforms": ["linux", "macOS", "windows"],
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"core:default"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Field reference:**
|
|
||||||
- `$schema` — Points to generated schema for IDE autocompletion. Use `desktop-schema.json` for desktop, `mobile-schema.json` for mobile.
|
|
||||||
- `identifier` — Unique capability name (ASCII lowercase, max 116 chars).
|
|
||||||
- `description` — Human-readable purpose.
|
|
||||||
- `windows` — Array of window labels this capability applies to. Use `["*"]` for all windows.
|
|
||||||
- `permissions` — Array of permission identifiers.
|
|
||||||
- `platforms` — Optional. Restricts to specific platforms. Defaults to all platforms if omitted.
|
|
||||||
|
|
||||||
### Permission Identifier Naming Convention
|
|
||||||
|
|
||||||
| Pattern | Meaning | Example |
|
|
||||||
|---------|---------|---------|
|
|
||||||
| `<plugin>:default` | Default permission set for a plugin | `fs:default` |
|
|
||||||
| `<plugin>:allow-<command>` | Allow a specific command | `fs:allow-read` |
|
|
||||||
| `<plugin>:deny-<command>` | Deny a specific command | `fs:deny-write` |
|
|
||||||
| `core:<module>:<permission>` | Core Tauri module permission | `core:event:allow-emit` |
|
|
||||||
|
|
||||||
### Common Permission Identifiers
|
|
||||||
|
|
||||||
**Core permissions:**
|
|
||||||
- `core:default` — Basic runtime permissions
|
|
||||||
- `core:path:default` — Path resolution
|
|
||||||
- `core:event:default` — Event system (includes listen/emit)
|
|
||||||
- `core:event:allow-listen` — Allow listening to events
|
|
||||||
- `core:event:allow-emit` — Allow emitting events
|
|
||||||
- `core:window:default` — Window management
|
|
||||||
- `core:window:allow-set-title` — Allow changing window title
|
|
||||||
|
|
||||||
**Plugin permissions:**
|
|
||||||
- `fs:allow-read`, `fs:allow-write`, `fs:allow-exists`, `fs:allow-mkdir`, `fs:allow-remove`
|
|
||||||
- `http:allow-request` — HTTP client requests
|
|
||||||
- `dialog:allow-open`, `dialog:allow-save`, `dialog:allow-message`
|
|
||||||
- `shell:allow-open`, `shell:allow-execute`
|
|
||||||
- `notification:allow-notify`, `notification:allow-is-permission-granted`
|
|
||||||
- `clipboard-manager:allow-read-text`, `clipboard-manager:allow-write-text`
|
|
||||||
- `websocket:allow-connect`, `websocket:allow-send`
|
|
||||||
- `store:default` — Persistent key-value store
|
|
||||||
|
|
||||||
### Fine-Grained Scopes
|
|
||||||
|
|
||||||
Scopes restrict what a permission can actually access:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"identifier": "secure-fs-capability",
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"identifier": "fs:allow-read",
|
|
||||||
"allow": [{ "path": "$APPDATA/logs/*" }, { "path": "$HOME/Documents/*.txt" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "fs:allow-write",
|
|
||||||
"allow": [{ "path": "$APPDATA/logs/*.log" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "fs:deny-write",
|
|
||||||
"deny": [{ "path": "$APPDATA/config/*" }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Available scope variables: `$APPDATA`, `$HOME`, `$APPCONFIG`, `$APPCACHE`, `$APPLOG`, `$RESOURCE`, `$EXE`.
|
|
||||||
|
|
||||||
### Restricting Custom Commands
|
|
||||||
|
|
||||||
By default, all `#[tauri::command]` functions are accessible from all windows. To restrict a custom command to specific windows only, use `build.rs`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn main() {
|
|
||||||
tauri_build::try_build(
|
|
||||||
tauri_build::Attributes::new()
|
|
||||||
.app_manifest(
|
|
||||||
tauri_build::AppManifest::new()
|
|
||||||
.commands(&["my_restricted_command", "admin_only_command"])
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then grant access in the capability:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"identifier": "admin-capability",
|
|
||||||
"windows": ["admin-panel"],
|
|
||||||
"permissions": [
|
|
||||||
{ "identifier": "allow-my-restricted-command" },
|
|
||||||
{ "identifier": "allow-admin-only-command" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Referencing Capabilities Explicitly
|
|
||||||
|
|
||||||
By default, all capability files in `src-tauri/capabilities/` are auto-discovered. To control which ones are used explicitly:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"security": {
|
|
||||||
"capabilities": ["my-capability", "admin-capability"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline Capabilities
|
|
||||||
|
|
||||||
You can define capabilities directly in `tauri.conf.json` instead of separate files:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"security": {
|
|
||||||
"capabilities": [
|
|
||||||
{
|
|
||||||
"identifier": "inline-cap",
|
|
||||||
"windows": ["*"],
|
|
||||||
"permissions": ["core:default"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Rust Backend: `lib.rs` & Commands
|
|
||||||
|
|
||||||
### Application Builder Pattern
|
|
||||||
|
|
||||||
All Tauri 2.0 applications follow the `tauri::Builder` pattern:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
pub fn run() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
.plugin(tauri_plugin_opener::init()) // Register plugins
|
|
||||||
.plugin(tauri_plugin_dialog::init())
|
|
||||||
.plugin(tauri_plugin_fs::init())
|
|
||||||
.setup(|app| { // Setup hook (runs once at startup)
|
|
||||||
// Initialize resources, spawn servers, etc.
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.manage(AppState { /* ... */ }) // Inject managed state
|
|
||||||
.invoke_handler(tauri::generate_handler![ // Register commands
|
|
||||||
my_command,
|
|
||||||
another_command,
|
|
||||||
module::command_in_module
|
|
||||||
])
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running tauri application");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical rules:**
|
|
||||||
- `invoke_handler` can only be called **once**. All commands must be in a single `generate_handler![]` call.
|
|
||||||
- Commands defined in `lib.rs` **cannot** be marked `pub`.
|
|
||||||
- Commands defined in separate modules **must** be marked `pub`.
|
|
||||||
- The `#[cfg_attr(mobile, tauri::mobile_entry_point)]` attribute is required for the `run()` function.
|
|
||||||
|
|
||||||
### Defining Commands
|
|
||||||
|
|
||||||
Commands are the primary IPC mechanism. They are Rust functions annotated with `#[tauri::command]` and callable from the frontend via `invoke()`.
|
|
||||||
|
|
||||||
#### Basic Command
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[tauri::command]
|
|
||||||
fn greet(name: String) -> String {
|
|
||||||
format!("Hello, {}! Welcome from Rust.", name)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Command with Error Handling
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[tauri::command]
|
|
||||||
fn login(user: String, password: String) -> Result<String, String> {
|
|
||||||
if user == "admin" && password == "secret" {
|
|
||||||
Ok("authenticated".to_string())
|
|
||||||
} else {
|
|
||||||
Err("invalid credentials".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Async Command
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[tauri::command]
|
|
||||||
async fn fetch_data(url: String) -> Result<String, String> {
|
|
||||||
let response = reqwest::get(&url)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
response.text().await.map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**IMPORTANT limitation**: Async commands cannot accept borrowed references (`&str`, `&Path`) directly. Convert to owned types (`String`, `PathBuf`) or wrap the return in `Result`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Option A: Use owned types
|
|
||||||
#[tauri::command]
|
|
||||||
async fn process(value: String) -> String { /* ... */ }
|
|
||||||
|
|
||||||
// Option B: Wrap return in Result
|
|
||||||
#[tauri::command]
|
|
||||||
async fn process(value: &str) -> Result<String, ()> { /* ... */ }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Custom Error Types (Recommended)
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
enum AppError {
|
|
||||||
#[error("IO error: {0}")]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("Network error: {0}")]
|
|
||||||
Network(#[from] reqwest::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement Serialize so the error can cross the IPC boundary
|
|
||||||
impl serde::Serialize for AppError {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::ser::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.to_string().as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn read_config() -> Result<String, AppError> {
|
|
||||||
let content = std::fs::read_to_string("config.toml")?;
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Structured Error with Kind/Tag
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
enum AppError {
|
|
||||||
#[error(transparent)]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("{0}")]
|
|
||||||
Custom(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
#[serde(tag = "kind", content = "message")]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
enum ErrorKind {
|
|
||||||
Io(String),
|
|
||||||
Custom(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::Serialize for AppError {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::ser::Serializer,
|
|
||||||
{
|
|
||||||
let kind = match self {
|
|
||||||
Self::Io(e) => ErrorKind::Io(e.to_string()),
|
|
||||||
Self::Custom(msg) => ErrorKind::Custom(msg.clone()),
|
|
||||||
};
|
|
||||||
kind.serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Frontend receives: `{ kind: 'io', message: '...' }` — making it easy to handle errors by type.
|
|
||||||
|
|
||||||
#### Accessing Special Objects in Commands
|
|
||||||
|
|
||||||
**AppHandle** — Access to the application instance:
|
|
||||||
```rust
|
|
||||||
#[tauri::command]
|
|
||||||
fn do_something(app_handle: tauri::AppHandle) {
|
|
||||||
let app_dir = app_handle.path().app_dir();
|
|
||||||
// Spawn windows, emit events, access state...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**WebviewWindow** — The window that invoked the command:
|
|
||||||
```rust
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_window_info(webview_window: tauri::WebviewWindow) -> String {
|
|
||||||
webview_window.label().to_string()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Managed State** — Shared application state:
|
|
||||||
```rust
|
|
||||||
use tauri::State;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
struct DbConnection(Mutex<String>);
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn query_database(state: State<'_, DbConnection>) -> String {
|
|
||||||
let conn = state.0.lock().unwrap();
|
|
||||||
conn.clone()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Raw Request** — Access headers and raw body:
|
|
||||||
```rust
|
|
||||||
#[tauri::command]
|
|
||||||
fn upload(request: tauri::ipc::Request) -> Result<(), String> {
|
|
||||||
let tauri::ipc::InvokeBody::Raw(data) = request.body() else {
|
|
||||||
return Err("Expected raw body".to_string());
|
|
||||||
};
|
|
||||||
let auth = request.headers().get("Authorization")
|
|
||||||
.ok_or("Missing auth header")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Commands in Separate Modules
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src-tauri/src/commands.rs
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn create_user(name: String) -> String {
|
|
||||||
format!("Created user: {}", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn delete_user(id: u32) -> bool {
|
|
||||||
// ...
|
|
||||||
true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src-tauri/src/lib.rs
|
|
||||||
mod commands;
|
|
||||||
|
|
||||||
pub fn run() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
|
||||||
commands::create_user,
|
|
||||||
commands::delete_user
|
|
||||||
])
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The `commands::` prefix is Rust path resolution only — the frontend still calls `invoke('create_user', ...)` without any prefix.
|
|
||||||
|
|
||||||
### Returning Large Data (ArrayBuffers)
|
|
||||||
|
|
||||||
For large binary data like file contents, use `tauri::ipc::Response`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::ipc::Response;
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn read_file() -> Response {
|
|
||||||
let data = std::fs::read("/path/to/file").unwrap();
|
|
||||||
Response::new(data)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Streaming Data (Channels)
|
|
||||||
|
|
||||||
For streaming large amounts of data to the frontend:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::ipc::Channel;
|
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn stream_file(path: std::path::PathBuf, on_chunk: Channel<Vec<u8>>) {
|
|
||||||
let mut file = tokio::fs::File::open(path).await.unwrap();
|
|
||||||
let mut buf = vec![0u8; 4096];
|
|
||||||
loop {
|
|
||||||
let n = file.read(&mut buf).await.unwrap();
|
|
||||||
if n == 0 { break; }
|
|
||||||
on_chunk.send(&buf[..n]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Frontend Integration Patterns
|
|
||||||
|
|
||||||
### Using `@tauri-apps/api` npm Package (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @tauri-apps/api
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Imports in v2 — NOTE the path changes from v1
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import { listen, emit, emitTo, once } from '@tauri-apps/api/event';
|
|
||||||
import { getCurrentWebviewWindow, WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
|
||||||
import { Channel } from '@tauri-apps/api/core';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Global `window.__TAURI__` (No npm package needed)
|
|
||||||
|
|
||||||
Requires `app.withGlobalTauri: true` in `tauri.conf.json`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// No import needed — accessed via the global object
|
|
||||||
const { invoke } = window.__TAURI__.core;
|
|
||||||
const { listen, emit } = window.__TAURI__.event;
|
|
||||||
```
|
|
||||||
|
|
||||||
This pattern is ideal for vanilla JS projects that don't use a bundler, avoiding the need for any npm packages beyond `@tauri-apps/cli`.
|
|
||||||
|
|
||||||
### Import Path Changes from v1
|
|
||||||
|
|
||||||
| v1 Import | v2 Import |
|
|
||||||
|-----------|-----------|
|
|
||||||
| `@tauri-apps/api/tauri` | `@tauri-apps/api/core` |
|
|
||||||
| `@tauri-apps/api/window` | `@tauri-apps/api/webviewWindow` |
|
|
||||||
| `@tauri-apps/api/cli` | `@tauri-apps/plugin-cli` |
|
|
||||||
| `@tauri-apps/api/clipboard` | `@tauri-apps/plugin-clipboard-manager` |
|
|
||||||
| `@tauri-apps/api/dialog` | `@tauri-apps/plugin-dialog` |
|
|
||||||
| `@tauri-apps/api/fs` | `@tauri-apps/plugin-fs` |
|
|
||||||
| `@tauri-apps/api/global-shortcut` | `@tauri-apps/plugin-global-shortcut` |
|
|
||||||
| `@tauri-apps/api/http` | `@tauri-apps/plugin-http` |
|
|
||||||
| `@tauri-apps/api/notification` | `@tauri-apps/plugin-notification` |
|
|
||||||
| `@tauri-apps/api/shell` | `@tauri-apps/plugin-shell` |
|
|
||||||
| `@tauri-apps/api/updater` | `@tauri-apps/plugin-updater` |
|
|
||||||
| `@tauri-apps/api/os` | `@tauri-apps/plugin-os` |
|
|
||||||
| `@tauri-apps/api/process` | `@tauri-apps/plugin-process` |
|
|
||||||
|
|
||||||
**Rule of thumb in v2**: If it's not `core`, `event`, or `webviewWindow`, it's a plugin.
|
|
||||||
|
|
||||||
### Plugin JS Package Naming
|
|
||||||
|
|
||||||
All v2 plugin JS packages follow: `@tauri-apps/plugin-<name>`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @tauri-apps/plugin-store
|
|
||||||
npm install @tauri-apps/plugin-fs
|
|
||||||
npm install @tauri-apps/plugin-http
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. IPC: Commands, Events & Channels
|
|
||||||
|
|
||||||
### Commands (Frontend → Rust)
|
|
||||||
|
|
||||||
The primary request-response mechanism:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Frontend (v2 import)
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
|
|
||||||
// Simple invocation
|
|
||||||
const result = await invoke('greet', { name: 'World' });
|
|
||||||
console.log(result); // "Hello, World! Welcome from Rust."
|
|
||||||
|
|
||||||
// With error handling
|
|
||||||
try {
|
|
||||||
const token = await invoke('login', { user: 'admin', password: 'secret' });
|
|
||||||
console.log('Authenticated:', token);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Login failed:', error);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Argument naming convention**: Arguments are passed as a JSON object with **camelCase** keys by default. Use `#[tauri::command(rename_all = "snake_case")]` to accept snake_case from the frontend.
|
|
||||||
|
|
||||||
### Events (Bidirectional, Multi-Consumer)
|
|
||||||
|
|
||||||
Events are fire-and-forget messages. Use them for notifications, streaming status updates, or any data that multiple components might need.
|
|
||||||
|
|
||||||
#### Emitting Events from Rust
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::{AppHandle, Emitter};
|
|
||||||
|
|
||||||
// Global event (all listeners receive it)
|
|
||||||
app_handle.emit("download-progress", 42)?;
|
|
||||||
|
|
||||||
// Target a specific window
|
|
||||||
app_handle.emit_to("settings-panel", "config-changed", new_config)?;
|
|
||||||
|
|
||||||
// Filter to specific windows
|
|
||||||
use tauri::EventTarget;
|
|
||||||
app_handle.emit_filter("notification", payload, |target| {
|
|
||||||
matches!(target, EventTarget::WebviewWindow { label } if label == "main")
|
|
||||||
})?;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Listening for Events on the Frontend
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { listen, once, emit, emitTo } from '@tauri-apps/api/event';
|
|
||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
|
||||||
|
|
||||||
// Global listen (receives ALL events with this name)
|
|
||||||
const unlisten = await listen('download-progress', (event) => {
|
|
||||||
console.log(`Progress: ${event.payload}%`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Targeted listen (only events emitted to THIS window)
|
|
||||||
const appWebview = getCurrentWebviewWindow();
|
|
||||||
const unlisten2 = await appWebview.listen('config-changed', (event) => {
|
|
||||||
console.log('New config:', event.payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen once, then auto-cleanup
|
|
||||||
await once('initialization-complete', (event) => {
|
|
||||||
console.log('App ready!', event.payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emit from frontend to Rust
|
|
||||||
await emit('user-action', { type: 'click', target: 'button' });
|
|
||||||
|
|
||||||
// Emit to a specific window from frontend
|
|
||||||
const target = new WebviewWindow('settings-panel');
|
|
||||||
await target.emit('settings-request', { key: 'theme' });
|
|
||||||
```
|
|
||||||
|
|
||||||
**Always call `unlisten()`** when the component unmounts or the listener is no longer needed to prevent memory leaks.
|
|
||||||
|
|
||||||
#### Listening for Events on the Rust Side
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::{Listener, Manager};
|
|
||||||
|
|
||||||
pub fn run() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
.setup(|app| {
|
|
||||||
// Listen globally
|
|
||||||
app.listen("user-action", |event| {
|
|
||||||
println!("Received: {}", event.payload());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen on a specific window
|
|
||||||
let main = app.get_webview_window("main").unwrap();
|
|
||||||
main.listen("config-changed", |event| {
|
|
||||||
println!("Config updated: {}", event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen once
|
|
||||||
app.once("ready", |event| {
|
|
||||||
println!("App is ready!");
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Channels (High-Throughput Streaming)
|
|
||||||
|
|
||||||
Channels are the recommended mechanism for streaming large amounts of ordered data from Rust to the frontend. They are faster and more memory-efficient than events for high-throughput scenarios.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::ipc::Channel;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
|
||||||
#[serde(tag = "event", content = "data")]
|
|
||||||
enum StreamEvent {
|
|
||||||
Started { total: usize },
|
|
||||||
Progress { current: usize, chunk: Vec<u8> },
|
|
||||||
Finished { total: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn download_file(url: String, on_event: Channel<StreamEvent>) {
|
|
||||||
on_event.send(StreamEvent::Started { total: 1000 }).unwrap();
|
|
||||||
// ... streaming logic ...
|
|
||||||
on_event.send(StreamEvent::Finished { total: 1000 }).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { invoke, Channel } from '@tauri-apps/api/core';
|
|
||||||
|
|
||||||
const onEvent = new Channel();
|
|
||||||
onEvent.onmessage = (event) => {
|
|
||||||
switch (event.event) {
|
|
||||||
case 'started': console.log(`Starting: ${event.data.total} bytes`); break;
|
|
||||||
case 'progress': /* handle chunk */ break;
|
|
||||||
case 'finished': console.log('Done!'); break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await invoke('download_file', { url: 'https://...', onEvent });
|
|
||||||
```
|
|
||||||
|
|
||||||
### Evaluating JavaScript from Rust
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
// In a command or setup hook
|
|
||||||
let webview = app.get_webview_window("main").unwrap();
|
|
||||||
webview.eval("document.getElementById('status').textContent = 'Loaded!'")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
For complex data passing, use the `serialize-to-javascript` crate.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Window Management
|
## 8. Window Management
|
||||||
|
|
||||||
### Creating Windows Dynamically
|
### Creating Windows Dynamically
|
||||||
@ -1814,3 +904,7 @@ app.get_webview_window("label")?.set_focus()?;
|
|||||||
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
|
- Calling Frontend: https://v2.tauri.app/develop/calling-frontend/
|
||||||
- Plugin Index: https://v2.tauri.app/plugin/
|
- Plugin Index: https://v2.tauri.app/plugin/
|
||||||
- Rust Docs: https://docs.rs/tauri/latest/tauri/
|
- Rust Docs: https://docs.rs/tauri/latest/tauri/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Back to**: [Part 1: Core Architecture, Config, Commands & IPC](./01-core-architecture-config-commands-ipc.md)
|
||||||
Loading…
Reference in New Issue
Block a user