Event-Driven Communication Flow
Relevant source files
The following files were used as context for generating this wiki page:
The Event-Driven Communication Flow in the Tauri Centrifugo plugin facilitates real-time, asynchronous interactions between a Tauri frontend and a Centrifugo server. This architecture ensures that connection states, channel subscriptions, and incoming publications are handled via a robust event emission system, allowing the frontend to react dynamically to server-side changes without constant polling.
The flow is bridged by the Rust backend, which manages a persistent WebSocket connection using the tokio-centrifuge library and translates internal library callbacks into Tauri events. These events are then captured by the TypeScript/JavaScript guest API, which provides convenient listener hooks for developers.
Sources: README.md:12-20, src/lib.rs:36-42, src/core.rs:18-35
Architecture and Core Components
The communication flow is divided into three distinct layers: the Frontend API (Guest JS), the Plugin Bridge (Rust Commands), and the Core Client (Rust Core). The Centrifugo struct in the core module serves as the central state manager, holding the Tokio runtime and the active Centrifugo client.
The diagram above illustrates the multi-layered architecture where the Rust Core acts as a bidirectional mediator between the external server and the frontend listeners.
Sources: src/core.rs:60-75, src/lib.rs:90-110, guest-js/index.ts:160-180
Core State Management
The backend utilizes Arc<Mutex<Option<Client>>> and Arc<Mutex<HashMap<String, bool>>> to manage the client instance and channel subscription states across asynchronous tasks. This ensures thread-safe access when commands are invoked from the frontend or when the background task processes server events.
Sources: src/core.rs:30-40, src/core.rs:71-75
Connection Lifecycle Events
When a connection is initiated via the connect command, the core logic spawns a background task that sets up several critical callbacks. These callbacks map internal tokio-centrifuge states to Tauri events prefixed with centrifugo:.
| Event Name | Trigger Condition | Payload Type |
|---|---|---|
centrifugo:connecting | Connection attempt initiated | ConnectionStatus (Timestamp) |
centrifugo:connected | WebSocket successfully established | ConnectionStatus (Timestamp) |
centrifugo:disconnected | Connection closed or lost | ConnectionStatus (Timestamp) |
centrifugo:error | Connection or protocol error occurred | ErrorData (Message) |
Sources: src/core.rs:125-165, src/models.rs:142-161, guest-js/index.ts:200-215
Connection Sequence
The following sequence diagram represents the typical connection flow from the frontend invocation to event reception.
Sequence of connection establishment and the subsequent asynchronous event emission.
Sources: src/core.rs:105-120, guest-js/index.ts:100-105, examples/tauri-app/src/App.svelte:78-100
Channel and Subscription Flow
Subscriptions can be defined during the initial connection or added dynamically. For every channel, the core logic creates a new_subscription object and attaches specific event handlers for that channel's lifecycle.
Subscription Events
centrifugo:subscribing: Emitted when a request to join a channel is sent.centrifugo:subscribed: Emitted when the server confirms the subscription.centrifugo:unsubscribed: Emitted when a channel is left.centrifugo:publication: Emitted when a message is received on a specific channel.
Sources: src/core.rs:175-215, guest-js/index.ts:220-235
Publication Handling
Incoming publications contain base64-encoded data which the plugin emits to the frontend. The guest-js API provides helper functions to automatically decode these messages.
// guest-js/index.ts:237-252
export const onMessage = async (callback: (data: { channel: string; data: any }) => void): Promise<UnlistenFn> =>
await onCentrifugoEvent('publication', (pubData: TauriEventWrapper<PublicationData>) => {
try {
const decodedData = utils.decodeJson(pubData.payload.data)
callback({
channel: pubData.payload.channel,
data: decodedData
})
} catch (error) {
// Return raw data if JSON decoding fails
callback({ channel: pubData.payload.channel, data: pubData.payload.data })
}
})
Sources: guest-js/index.ts:237-252, src/core.rs:205-215
Data Structures for Events
The plugin uses structured models to ensure consistency between the Rust backend and JavaScript frontend. All events are wrapped in a TauriEventWrapper on the JS side.
Event Data Models (Rust)
// src/models.rs:163-172
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PublicationData {
pub channel: String,
pub data: String, // Base64-encoded
pub timestamp: i64,
}
Sources: src/models.rs:163-172, src/models.rs:142-152
Event Wrapper (TypeScript)
// guest-js/index.ts:80-84
export interface TauriEventWrapper<T> {
event: string
payload: T
id: number
}
Sources: guest-js/index.ts:80-84
Implementation Example
In a practical application like the Svelte example, listeners are set up during component mounting to log activity.
// examples/tauri-app/src/App.svelte:78-95
unlisteners = [
await onConnecting((eventData) => {
connectionStatus = 'Connecting...';
addToLog(`🔄 Attempting to connect...`);
}),
await onConnected((eventData) => {
connectionStatus = 'Connected';
addToLog(`✅ Successfully connected!`);
}),
await onPublication((eventData) => {
const data = eventData.payload;
addToLog(`📨 Message received on channel ${data.channel}`);
})
];
Sources: examples/tauri-app/src/App.svelte:78-100, guest-js/index.ts:210-230
Conclusion
The Event-Driven Communication Flow provides a seamless bridge between Centrifugo's real-time capabilities and Tauri's desktop/mobile environment. By leveraging background tasks in Rust and an asynchronous listener pattern in JavaScript, the plugin allows for highly responsive applications that remain synchronized with server-side state changes in real-time.
Sources: README.md:5-10, src/core.rs:20-30