Tauri IPC primitives: Events and Commands
Tauri provides two main Inter-Process Communication primitives: Events and Commands. Events are fire-and-forget, one-way messages suited for lifecycle events and state changes, and can be emitted by both the Frontend and Tauri Core. Commands use a foreign function interface-like abstraction that allows the Frontend to invoke Rust functions, pass arguments, and receive data, using a JSON-RPC like protocol where all arguments and return data must be serializable to JSON.
Tauri API is a TypeScript library for frontend-backend communication
The Tauri API is a TypeScript library that creates CommonJS and ES Module JavaScript endpoints to import into frontend frameworks so that the Webview can call and listen to backend activity. It ships in pure TypeScript and uses message passing of webviews to their hosts.
Channels for streaming data
Channels are designed to be fast and deliver ordered data. They are used internally for streaming operations such as download progress, child process output, and WebSocket messages. Channels are more suitable than the event system for sending large amounts of data.
Channel usage in command example
Example of using channels to stream download events from a Rust command:
```rust
use tauri::{AppHandle, ipc::Channel};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", rename_all_fields = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
Started {
url: &'a str,
download_id: usize,
content_length: usize,
},
Progress {
download_id: usize,
chunk_length: usize,
},
Finished {
download_id: usize,
},
}
#[tauri::command]
fn download(app: AppHandle, url: String, on_event: Channel<DownloadEvent>) {
let content_length = 1000;
let download_id = 1;
on_event.send(DownloadEvent::Started {
url: &url,
download_id,
content_length,
}).unwrap();
for chunk_length in [15, 150, 35, 500, 300] {
on_event.send(DownloadEvent::Progress {
download_id,
chunk_length,
}).unwrap();
}
on_event.send(DownloadEvent::Finished { download_id }).unwrap();
}
```
Channel invocation from TypeScript frontend
Example of invoking a command with channels from TypeScript:
```ts
import { invoke, Channel } from '@tauri-apps/api/core';
type DownloadEvent =
| {
event: 'started';
data: {
url: string;
downloadId: number;
contentLength: number;
};
}
| {
event: 'progress';
data: {
downloadId: number;
chunkLength: number;
};
}
| {
event: 'finished';
data: {
downloadId: number;
};
};
const onEvent = new Channel<DownloadEvent>();
onEvent.onmessage = (message) => {
console.log(`got download event ${message.event}`);
};
await invoke('download', {
url: 'https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-schema-generator/schemas/config.schema.json',
onEvent,
});
```
WebviewWindow#eval for executing JavaScript
Use WebviewWindow#eval to directly execute JavaScript code in the webview context from Rust.
WebviewWindow#eval example
Example of evaluating JavaScript from Rust:
```rust
use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let webview = app.get_webview_window("main").unwrap();
webview.eval("console.log('hello from Rust')")?;
Ok(())
})
```
serialize-to-javascript crate for complex scripts
When evaluating JavaScript code that needs to use input from Rust objects, use the serialize-to-javascript crate to safely serialize Rust data into JavaScript.
Spawn sidecar from Rust using ShellExt trait
Import the 'tauri_plugin_shell::ShellExt' trait and use 'app.shell().sidecar(name)' to spawn a sidecar from Rust. The sidecar() function expects only the filename, not the full path from externalBin. Sidecar is spawned asynchronously and returns a receiver for events and a child process handle. The plugin must be initialized before use according to the shell plugin guide.
Rust example: spawn sidecar and read stdout events
use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
use tauri::Emitter;
let sidecar_command = app.shell().sidecar("my-sidecar").unwrap();
let (mut rx, mut child) = sidecar_command
.spawn()
.expect("Failed to spawn sidecar");
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
if let CommandEvent::Stdout(line_bytes) = event {
let line = String::from_utf8_lossy(&line_bytes);
app
.emit("message", Some(format!("'{}'", line)))
.expect("failed to emit event");
child.write("message from Rust\n".as_bytes()).unwrap();
}
}
});
Sidecar function expects filename not full path
When calling app.shell().sidecar(name), the name parameter must be only the filename of the sidecar, not the full path from the externalBin configuration. For example, with externalBin: ['binaries/app', 'my-sidecar', '../scripts/sidecar'], call sidecar with 'app', 'my-sidecar', or 'sidecar', not 'binaries/app' or '../scripts/sidecar'.
Call sidecar from JavaScript using Command.sidecar
Import the Command class from '@tauri-apps/plugin-shell' and use the static method Command.sidecar(path) to create a sidecar command. The path string must match exactly one of the strings defined in the externalBin configuration array. Then call execute() or spawn() on the command object.
Pass arguments to sidecar in Rust
Call .args() method on the sidecar command with an array of argument strings. Arguments must match exactly what is specified in the capabilities configuration in both value and order.
Rust example: sidecar with arguments
use tauri_plugin_shell::ShellExt;
#[tauri::command]
async fn call_my_sidecar(app: tauri::AppHandle) {
let sidecar_command = app
.shell()
.sidecar("my-sidecar")
.unwrap()
.args(["arg1", "-a", "--arg2", "any-string-that-matches-the-validator"]);
let (mut _rx, mut _child) = sidecar_command.spawn().unwrap();
}
JavaScript example: sidecar with arguments
import { Command } from '@tauri-apps/plugin-shell';
const command = Command.sidecar('binaries/my-sidecar', [
'arg1',
'-a',
'--arg2',
'any-string-that-matches-the-validator',
]);
const output = await command.execute();
JavaScript to Rust bindings in Tauri
Bindings between JavaScript and Rust are available to developers using the invoke function in JavaScript. This allows frontend code to call Rust backend functions.
Swift and Kotlin bindings for Tauri
Swift and Kotlin bindings are available for Tauri Plugins, allowing developers to integrate backend logic in these languages.
Tauri v2 IPC uses custom protocols instead of serialized strings
The v2 Inter-Process Communication system has been revamped to use custom protocols rather than the v1 approach which serialized all messages to strings. The new implementation provides better performance and functionality similar to how webviews handle HTTP-based communication.
Tauri v2 adds channel API for Rust to frontend communication
Tauri v2 introduces a new channel API that allows quick sending of data from Rust to the frontend without requiring string serialization.
IPC rewrite: Raw Payloads support
Tauri 2.0 rewrote the IPC layer to support Raw Payloads and Raw Requests. Previously all IPC payloads were JSON serialized and deserialized, causing overhead when transferring more than a few kilobytes. The new system supports raw bytes directly or custom (de)serialization processes such as bson, protobuf, or avro. For directly reading files from the filesystem into the WebView, the convertFileSrc functionality is still recommended as it is likely faster if data does not need processing on the Rust backend.
Channel IPC type
Tauri 2.0 added tauri::ipc::Channel type and an equivalent JS Channel type to send data across the IPC.
Response error handling in URI scheme protocol
Tauri 2.0 changed tauri::Builder::register_uri_scheme_protocol to return a http::Response instead of Result<http::Response>. To return an error response, manually create a response with status code >= 400.