A crate that provides a custom invoke system for Tauri v2 using a localhost JSON-RPC WebSocket. Each message is delivered through WebSocket using the JSON-RPC 2.0 specification.
- JSON-RPC 2.0 over WebSocket - Full compatibility with the JSON-RPC 2.0 specification
- Async Support - Non-blocking request handling with configurable timeouts
- Event System - Emit events from Rust backend with
AwesomeEmit
and listen on frontend withAwesomeEvent
- Full Tauri Integration - Works seamlessly with existing Tauri commands and invoke handlers
- Type Safety - Supports all Tauri argument types including channels, binary data, and complex objects
Note: This version is compatible with Tauri v2.
Check out the example for a complete working demo.
First, add the dependency to your src-tauri/Cargo.toml
file:
[dependencies]
tauri-awesome-rpc = { git = "https://github.com/ahkohd/tauri-awesome-rpc", branch = "v2" }
Then, setup the Websocket JSON RPC invoke system on the main.rs
file:
use tauri::{Manager, WebviewWindow};
use tauri_awesome_rpc::{AwesomeEmit, AwesomeRpc};
use serde_json::json;
fn main() {
let allowed_origins = if cfg!(dev) {
vec!["http://localhost:1420", "http://localhost:5173"]
} else {
vec!["tauri://localhost"]
};
// Create with default 30 second timeout
let awesome_rpc = AwesomeRpc::new(allowed_origins);
// Or create with custom timeout
// use std::time::Duration;
// let awesome_rpc = AwesomeRpc::with_timeout(allowed_origins, Duration::from_secs(60));
tauri::Builder::default()
.invoke_system(awesome_rpc.initialization_script())
.setup(move |app| {
awesome_rpc.start(app.handle().clone());
Ok(())
})
.invoke_handler(tauri::generate_handler![test_command, report_time_elapsed])
.run(tauri::generate_context!())
.expect("error while running tauri application")
}
#[tauri::command]
fn test_command(args: u64) -> Result<String, ()> {
println!("executed command with args {:?}", args);
Ok("executed".into())
}
#[tauri::command]
fn report_time_elapsed(window: WebviewWindow) {
tauri::async_runtime::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(250));
let start_time = std::time::Instant::now();
loop {
interval.tick().await;
// emit an awesome event to the main window
window
.state::<AwesomeEmit>()
.emit("main", "time_elapsed", json!(start_time.elapsed()));
}
});
}
Then, on the frontend:
<html>
<body>
<div>
<h1>tauri-awesome-rpc</h1>
<h5>invoke test</h5>
<div id="response"></div>
<h5>AwesomeEvent.listen test</h5>
<div id="time_elapsed"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
- Use your Tauri
invoke
method as usual. - Use
window.AwesomeEvent
to listen to the events emitted usingAwesomeEmit
from the Rust backend.
import { invoke } from "@tauri-apps/api/tauri";
const response = document.getElementById("response") as HTMLDivElement;
const timeElapsed = document.getElementById("time_elapsed") as HTMLDivElement;
invoke("test_command", { args: 5 })
.then((data) => {
response.innerText = data as string;
})
.catch(console.error);
invoke("report_time_elapsed");
const _unsubscribe = window.AwesomeEvent.listen("time_elapsed", (data) => {
timeElapsed.innerText = JSON.stringify(data);
});
Add the following type definition to your project's global.d.ts
file:
interface Window {
AwesomeEvent: {
listen(eventName: string, callback: (data: any) => void): () => void;
};
}
Alternatively, you can use the provided TypeScript API from the guest-js
folder:
import { listen } from '@tauri-awesome-rpc/api';
const unlisten = listen('time_elapsed', (data) => {
console.log('Time elapsed:', data);
});
- WebSocket Server: A JSON-RPC WebSocket server runs on a dynamically allocated port
- Invoke Interception: The initialization script intercepts Tauri's
postMessage
calls - Request/Response Flow:
- Frontend sends invoke requests via WebSocket as JSON-RPC messages
- Rust backend processes the request through Tauri's standard invoke system
- Responses are sent back through the same WebSocket connection
- Event System: A separate WebSocket connection handles real-time events from backend to frontend
You can configure the timeout for invoke requests:
use std::time::Duration;
// Default 30 second timeout
let awesome_rpc = AwesomeRpc::new(allowed_origins);
// Custom timeout with milliseconds precision
let awesome_rpc = AwesomeRpc::with_timeout(allowed_origins, Duration::from_millis(5500));
Configure allowed origins based on your environment:
let allowed_origins = if cfg!(dev) {
vec!["http://localhost:1420", "http://localhost:5173"] // Development origins
} else {
vec!["tauri://localhost"] // Production origins
};