Skip to main contenttauri
Advanced Tauri event patterns for bidirectional communication, streaming data, window-to-window messaging, and custom event handling
跳到安装 用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill tauri命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
同仓库更多 Skills
LinkedIn automation via the Linked API CLI - fetch profiles, search people and companies, send messages, manage connections, create posts, react, comment, and run Sales Navigator and custom workflows. Use when the user wants to interact with LinkedIn.
Xquik X data automation API - Use REST or MCP for tweet search, user lookup, follower exports, media downloads, monitors, webhooks, giveaway draws, and confirmation-gated X actions.
MCP (Model Context Protocol) - Build AI-native servers with tools, resources, and prompts. TypeScript/Python SDKs for Claude Desktop integration.
| name | tauri |
| description | Advanced Tauri event patterns for bidirectional communication, streaming data, window-to-window messaging, and custom event handling |
| user-invocable | false |
| disable-model-invocation | true |
| version | 1.0.0 |
| category | development |
| author | Claude MPM Team |
| license | MIT |
| progressive_disclosure | {"entry_point":{"summary":"Advanced event patterns: bidirectional events, streaming, window messaging, custom payloads, listener management","when_to_use":"When implementing real-time updates, progress tracking, inter-window communication, or streaming data","quick_start":"1. Backend emits with window.emit() 2. Frontend listens with listen() 3. Clean up with unlisten() 4. Use typed payloads"}} |
| context_limit | 500 |
| tags | ["tauri","events","ipc","streaming","real-time"] |
| requires_tools | [] |
Tauri Advanced Event System
Event Fundamentals
Backend → Frontend Events
Basic event emission:
use tauri::Window;
#[tauri::command]
async fn start_download(
url: String,
window: Window,
) -> Result<(), String> {
window.emit("download-started", url)
.map_err(|e| e.to_string())?;
window.emit("download-complete", "Success")
.map_err(|e| e.to_string())
}
Frontend listener:
import { listen, UnlistenFn } from '@tauri-apps/api/event';
const unlisten = await listen<string>('download-started', (event) => {
console.log('Download started:', event.payload);
});
unlisten();
Structured Event Payloads
Typed Events with Serde
Backend:
use serde::Serialize;
#[derive(Serialize, Clone)]
struct ProgressEvent {
current: ,
total: ,
percentage: ,
message: ,
speed_mbps: <>,
}
(
url: ,
window: Window,
) <(), > {
= (&url).?;
..total_size {
= ProgressEvent {
current: chunk,
total: total_size,
percentage: (chunk / total_size ) * ,
message: (, chunk, total_size),
speed_mbps: (()),
};
window.(, progress)
.(|e| e.())?;
}
(())
}
usize
usize
f64
String
Option
f64
#[tauri::command]
async
fn
download_file
String
->
Result
String
let
total_size
get_file_size
await
for
chunk
in
0
let
progress
as
f64
as
f64
100.0
format!
"Downloading... {}/{}"
Some
calculate_speed
emit
"download-progress"
map_err
to_string
Ok
interface ProgressEvent {
current: number;
total: number;
percentage: number;
message: string;
speed_mbps?: number;
}
const unlisten = await listen<ProgressEvent>('download-progress', (event) => {
const { current, total, percentage, message, speed_mbps } = event.payload;
updateProgressBar(percentage);
updateStatus(message);
if (speed_mbps) {
updateSpeed(speed_mbps);
}
});
Complex Event Payloads
#[derive(Serialize, Clone)]
#[serde(tag = "type", content = "data")]
enum AppEvent {
UserLoggedIn { user_id: String, username: String },
UserLoggedOut { user_id: String },
DataSynced { items_count: usize, timestamp: String },
ErrorOccurred { code: String, message: String, recoverable: bool },
}
#[tauri::command]
async fn perform_login(
username: String,
password: String,
window: Window,
) -> Result<String, String> {
let user = authenticate(&username, &password).await?;
window.emit("app-event", AppEvent::UserLoggedIn {
user_id: user.id.clone(),
username: user.username.clone(),
}).map_err(|e| e.to_string())?;
Ok(user.id)
}
type AppEvent =
| { type: 'UserLoggedIn'; data: { user_id: string; username: string } }
| { type: 'UserLoggedOut'; data: { user_id: string } }
| { type: 'DataSynced'; data: { items_count: number; timestamp: string } }
| { type: 'ErrorOccurred'; data: { code: string; message: string; recoverable: boolean } };
listen<AppEvent>('app-event', (event) => {
const appEvent = event.payload;
switch (appEvent.type) {
case 'UserLoggedIn':
handleLogin(appEvent.data.user_id, appEvent.data.username);
break;
case 'UserLoggedOut':
handleLogout(appEvent.data.user_id);
break;
case 'DataSynced':
showSyncSuccess(appEvent.data.items_count);
break;
case 'ErrorOccurred':
handleError(appEvent.data);
break;
}
});
Streaming Data Patterns
Real-Time Data Stream
#[tauri::command]
async fn stream_sensor_data(
sensor_id: String,
window: Window,
) -> Result<(), String> {
let mut interval = tokio::time::interval(Duration::from_millis(100));
for _ in 0..100 {
interval.tick().await;
let reading = read_sensor(&sensor_id).await?;
window.emit("sensor-reading", reading)
.map_err(|e| e.to_string())?;
}
window.emit("sensor-stream-ended", sensor_id)
.map_err(|e| e.to_string())
}
import { useEffect, useState } from 'react';
import { listen } from '@tauri-apps/api/event';
interface SensorReading {
value: number;
timestamp: number;
unit: string;
}
function SensorMonitor() {
const [readings, setReadings] = useState<SensorReading[]>([]);
useEffect(() => {
let unlisten: UnlistenFn | undefined;
listen<SensorReading>('sensor-reading', (event) => {
setReadings(prev => [...prev.slice(-99), event.payload]);
}).then(fn => unlisten = fn);
return () => unlisten?.();
}, []);
return (
<div>
{readings.map((r, i) => (
<div key={i}>{r.value} {r.unit}</div>
))}
</div>
);
}
Buffered Streaming
#[tauri::command]
async fn stream_logs(
log_file: String,
window: Window,
) -> Result<(), String> {
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::fs::File;
let file = File::open(log_file).await
.map_err(|e| e.to_string())?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
let mut buffer = Vec::new();
while let Some(line) = lines.next_line().await
.map_err(|e| e.to_string())? {
buffer.push(line);
if buffer.len() >= 10 {
window.emit("log-batch", buffer.clone())
.map_err(|e| e.to_string())?;
buffer.clear();
}
}
if !buffer.is_empty() {
window.emit("log-batch", buffer)
.map_err(|e| e.to_string())?;
}
Ok(())
}
Multi-Window Communication
Broadcasting to All Windows
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn broadcast_message(
message: String,
app: AppHandle,
) -> Result<(), String> {
app.emit_all("broadcast", message)
.map_err(|e| e.to_string())
}
Targeted Window Messaging
#[tauri::command]
async fn send_to_window(
target_window: String,
message: String,
app: AppHandle,
) -> Result<(), String> {
if let Some(window) = app.get_window(&target_window) {
window.emit("private-message", message)
.map_err(|e| e.to_string())?;
Ok(())
} else {
Err(format!("Window '{}' not found", target_window))
}
}
Window-to-Window via Backend
import { invoke } from '@tauri-apps/api/core';
async function sendToSettings(data: any) {
await invoke('relay_to_settings', { data });
}
#[tauri::command]
async fn relay_to_settings(
data: serde_json::Value,
app: AppHandle,
) -> Result<(), String> {
if let Some(settings_window) = app.get_window("settings") {
settings_window.emit("data-update", data)
.map_err(|e| e.to_string())?;
}
Ok(())
}
Window B (receiver - settings):
import { listen } from '@tauri-apps/api/event';
useEffect(() => {
let unlisten: UnlistenFn | undefined;
listen('data-update', (event) => {
console.log('Received from main window:', event.payload);
updateSettings(event.payload);
}).then(fn => unlisten = fn);
return () => unlisten?.();
}, []);
Frontend → Backend Events
Custom Frontend Events
import { emit } from '@tauri-apps/api/event';
await emit('user-action', {
action: 'button-click',
button_id: 'save-button',
timestamp: Date.now()
});
use tauri::{Manager, Listener};
fn main() {
tauri::Builder::default()
.setup(|app| {
let app_handle = app.handle();
app_handle.listen_global("user-action", move |event| {
if let Some(payload) = event.payload() {
println!("User action: {}", payload);
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Advanced Listener Management
React Hook for Events
import { useEffect, useState } from 'react';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
function useEvent<T>(eventName: string): T | null {
const [payload, setPayload] = useState<T | null>(null);
useEffect(() => {
let unlisten: UnlistenFn | undefined;
listen<T>(eventName, (event) => {
setPayload(event.payload);
}).then(fn => unlisten = fn);
return () => unlisten?.();
}, [eventName]);
return payload;
}
function ProgressDisplay() {
const progress = useEvent<ProgressEvent>('download-progress');
if (!progress) return null;
return (
<div>
Progress: {progress.percentage.toFixed(2)}%
</div>
);
}
Event Queue Pattern
import { listen } from '@tauri-apps/api/event';
class EventQueue<T> {
private queue: T[] = [];
private unlisten?: UnlistenFn;
async start(eventName: string) {
this.unlisten = await listen<T>(eventName, (event) => {
this.queue.push(event.payload);
});
}
dequeue(): T | undefined {
return this.queue.shift();
}
clear() {
this.queue = [];
}
stop() {
this.unlisten?.();
}
get length() {
return this.queue.length;
}
}
const progressQueue = new EventQueue<ProgressEvent>();
await progressQueue.start('download-progress');
setInterval(() => {
while (progressQueue.length > 0) {
const event = progressQueue.dequeue();
processProgress(event);
}
}, 100);
One-Time Events
import { once } from '@tauri-apps/api/event';
await once<string>('initialization-complete', (event) => {
console.log('App initialized:', event.payload);
startApp();
});
Error Handling in Events
Safe Event Emission
async fn emit_safe(window: &Window, event: &str, payload: impl Serialize) -> Result<(), String> {
window.emit(event, payload)
.map_err(|e| {
eprintln!("Failed to emit event '{}': {}", event, e);
e.to_string()
})
}
#[tauri::command]
async fn process_with_events(
window: Window,
) -> Result<(), String> {
emit_safe(&window, "processing-started", "Starting...")
.await?;
emit_safe(&window, "processing-complete", "Done!")
.await?;
Ok(())
}
Performance Considerations
Throttling Events
use std::time::{Duration, Instant};
#[tauri::command]
async fn high_frequency_updates(
window: Window,
) -> Result<(), String> {
let mut last_emit = Instant::now();
let throttle_duration = Duration::from_millis(100);
for i in 0..10000 {
let value = compute_value(i);
if last_emit.elapsed() >= throttle_duration {
window.emit("update", value)
.map_err(|e| e.to_string())?;
last_emit = Instant::now();
}
}
Ok(())
}
Batching Events
#[tauri::command]
async fn batch_updates(
window: Window,
) -> Result<(), String> {
let mut batch = Vec::new();
for item in process_items() {
batch.push(item);
if batch.len() >= 50 {
window.emit("batch-update", batch.clone())
.map_err(|e| e.to_string())?;
batch.clear();
}
}
if !batch.is_empty() {
window.emit("batch-update", batch)
.map_err(|e| e.to_string())?;
}
Ok(())
}
Best Practices
- Always clean up listeners - Use
unlisten() to prevent memory leaks
- Type event payloads - Define interfaces for type safety
- Use structured events - Tagged unions for multiple event types
- Throttle high-frequency events - Prevent overwhelming frontend
- Batch when possible - Reduce serialization overhead
- Handle errors gracefully - Log failed emissions, don't crash
- Use once() for one-time events - Initialization, completion signals
- Namespace event names - Use prefixes like "download:", "user:", "system:"
Common Pitfalls
❌ Forgetting to unlisten:
function Component() {
listen('my-event', handler);
}
function Component() {
useEffect(() => {
let unlisten: UnlistenFn | undefined;
listen('my-event', handler).then(fn => unlisten = fn);
return () => unlisten?.();
}, []);
}
❌ Not handling serialization errors:
#[derive(Clone)]
struct Event { }
window.emit("event", Event {});
#[derive(Serialize, Clone)]
struct Event { }
❌ Emitting too frequently:
for i in 0..10000 {
window.emit("update", i);
}
Summary
- Events are async - Backend → Frontend communication
- Always type payloads - Use serde::Serialize + TypeScript interfaces
- Clean up listeners - Call
unlisten() in cleanup
- Throttle/batch - High-frequency events need rate limiting
- Use structured payloads - Tagged unions for multiple event types
- Window targeting -
emit() for specific, emit_all() for broadcast
- Frontend events - Use
emit() from frontend, listen in backend setup