一键导入
tauri-event-system
Advanced Tauri event patterns for bidirectional communication, streaming data, window-to-window messaging, and custom event handling
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Advanced Tauri event patterns for bidirectional communication, streaming data, window-to-window messaging, and custom event handling
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Download and transcribe videos from YouTube, Bilibili, TikTok and 1000+ platforms. Use when user requests video download, transcription (转录/字幕提取), or converting video to text/markdown. Supports quality selection, audio extraction, playlist downloads, cookie-based authentication, and AI-powered transcription via SiliconFlow API (免费转录).
Tauri v2 cross-platform app development with Rust backend. Use when configuring tauri.conf.json, implementing Rust commands (#[tauri::command]), setting up IPC patterns (invoke, emit, channels), configuring permissions/capabilities, troubleshooting build issues, or deploying desktop/mobile apps. Triggers on Tauri, src-tauri, invoke, emit, capabilities.json.
UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 9 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples.
Download and transcribe videos from YouTube, Bilibili, TikTok and 1000+ platforms. Use when user requests video download, transcription (转录/字幕提取), or converting video to text/markdown. Supports quality selection, audio extraction, playlist downloads, cookie-based authentication, and AI-powered transcription via SiliconFlow API (免费转录).
UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 9 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples.
UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 9 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples.
| name | tauri-event-system |
| description | Advanced Tauri event patterns for bidirectional communication, streaming data, window-to-window messaging, and custom event handling |
| 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 | [] |
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())?;
// Perform download...
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);
});
// Clean up when done
unlisten();
Backend:
use serde::Serialize;
#[derive(Serialize, Clone)]
struct ProgressEvent {
current: usize,
total: usize,
percentage: f64,
message: String,
speed_mbps: Option<f64>,
}
#[tauri::command]
async fn download_file(
url: String,
window: Window,
) -> Result<(), String> {
let total_size = get_file_size(&url).await?;
for chunk in 0..total_size {
// Download chunk...
let progress = ProgressEvent {
current: chunk,
total: total_size,
percentage: (chunk as f64 / total_size as f64) * 100.0,
message: format!("Downloading... {}/{}", chunk, total_size),
speed_mbps: Some(calculate_speed()),
};
window.emit("download-progress", progress)
.map_err(|e| e.to_string())?;
}
Ok(())
}
Frontend:
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);
}
});
#[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?;
// Emit structured event
window.emit("app-event", AppEvent::UserLoggedIn {
user_id: user.id.clone(),
username: user.username.clone(),
}).map_err(|e| e.to_string())?;
Ok(user.id)
}
Frontend:
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;
}
});
#[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())
}
Frontend with React:
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>
);
}
#[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);
// Send in batches of 10 lines
if buffer.len() >= 10 {
window.emit("log-batch", buffer.clone())
.map_err(|e| e.to_string())?;
buffer.clear();
}
}
// Send remaining lines
if !buffer.is_empty() {
window.emit("log-batch", buffer)
.map_err(|e| e.to_string())?;
}
Ok(())
}
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn broadcast_message(
message: String,
app: AppHandle,
) -> Result<(), String> {
// Emit to ALL windows
app.emit_all("broadcast", message)
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn send_to_window(
target_window: String,
message: String,
app: AppHandle,
) -> Result<(), String> {
// Get specific window
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 A (sender):
import { invoke } from '@tauri-apps/api/core';
async function sendToSettings(data: any) {
await invoke('relay_to_settings', { data });
}
Backend relay:
#[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?.();
}, []);
import { emit } from '@tauri-apps/api/event';
// Frontend emits event
await emit('user-action', {
action: 'button-click',
button_id: 'save-button',
timestamp: Date.now()
});
Backend listener:
use tauri::{Manager, Listener};
fn main() {
tauri::Builder::default()
.setup(|app| {
let app_handle = app.handle();
// Listen for frontend events
app_handle.listen_global("user-action", move |event| {
if let Some(payload) = event.payload() {
println!("User action: {}", payload);
// Process event...
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
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;
}
// Usage
function ProgressDisplay() {
const progress = useEvent<ProgressEvent>('download-progress');
if (!progress) return null;
return (
<div>
Progress: {progress.percentage.toFixed(2)}%
</div>
);
}
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;
}
}
// Usage
const progressQueue = new EventQueue<ProgressEvent>();
await progressQueue.start('download-progress');
// Process queue periodically
setInterval(() => {
while (progressQueue.length > 0) {
const event = progressQueue.dequeue();
processProgress(event);
}
}, 100);
import { once } from '@tauri-apps/api/event';
// Listen for event only once
await once<string>('initialization-complete', (event) => {
console.log('App initialized:', event.payload);
startApp();
});
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?;
// Process...
emit_safe(&window, "processing-complete", "Done!")
.await?;
Ok(())
}
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);
// Only emit every 100ms
if last_emit.elapsed() >= throttle_duration {
window.emit("update", value)
.map_err(|e| e.to_string())?;
last_emit = Instant::now();
}
}
Ok(())
}
#[tauri::command]
async fn batch_updates(
window: Window,
) -> Result<(), String> {
let mut batch = Vec::new();
for item in process_items() {
batch.push(item);
// Emit in batches of 50
if batch.len() >= 50 {
window.emit("batch-update", batch.clone())
.map_err(|e| e.to_string())?;
batch.clear();
}
}
// Emit remaining items
if !batch.is_empty() {
window.emit("batch-update", batch)
.map_err(|e| e.to_string())?;
}
Ok(())
}
unlisten() to prevent memory leaks❌ Forgetting to unlisten:
// WRONG - memory leak
function Component() {
listen('my-event', handler); // Never cleaned up!
}
// CORRECT
function Component() {
useEffect(() => {
let unlisten: UnlistenFn | undefined;
listen('my-event', handler).then(fn => unlisten = fn);
return () => unlisten?.();
}, []);
}
❌ Not handling serialization errors:
// WRONG - struct can't serialize
#[derive(Clone)] // Missing Serialize!
struct Event { }
window.emit("event", Event {}); // Runtime error!
// CORRECT
#[derive(Serialize, Clone)]
struct Event { }
❌ Emitting too frequently:
// WRONG - 10000 events in quick succession
for i in 0..10000 {
window.emit("update", i); // Overwhelming!
}
// CORRECT - throttle or batch
unlisten() in cleanupemit() for specific, emit_all() for broadcastemit() from frontend, listen in backend setup