Skip to main content

Tauri IPC reference

Ratspeak's desktop and mobile app is a Tauri v2 shell. The Rust core exposes a typed command surface that the WebView frontend calls; the core pushes asynchronous updates back as named events. This page documents the shape of that surface for app contributors, alternate frontends, automation, and test harnesses.

For the canonical, version-correct command list, browse the source on GitHub: https://github.com/ratspeak/Ratspeak.

How IPC works in Ratspeak

The Rust core registers just over 120 functions tagged #[tauri::command], grouped into nine domain modules. Each command is async, takes typed arguments, and returns an AppResult<T> where T is JSON-serializable. The WebView calls them via Tauri's invoke bridge.

In the other direction, the core stashes the Tauri AppHandle at startup and uses AppHandle::emit(name, payload) to broadcast events to every open window. There is no embedded HTTP server, no WebSocket, and no localhost port — all traffic is Tauri's process-internal IPC.

Because everything runs in-process, integrations must either live in the app's WebView or be Rust code calling the core directly. There is no remote IPC surface.

Calling a command

const { invoke } = window.__TAURI__.core;

try {
const conversations = await invoke("api_lxmf_conversations");
console.log(conversations);
} catch (err) {
// err is { code, message }
console.error(err.code, err.message);
}

For direct command parameters, use the Tauri argument names registered by Rust. Many Ratspeak commands take a single nested args struct; those nested fields intentionally mirror the Rust struct and may be snake_case, for example dest_hash, delivery_method, and client_msg_id in send_lxmf_message. Treat the source and dashboard callers as canonical when wiring a new integration.

Listening for events

const { listen } = window.__TAURI__.event;

const unlisten = await listen("lxmf_message", (e) => {
// e.payload is the event body, shape depends on event name
appendMessage(e.payload);
});

// later
unlisten();

Events fire on a single global channel — every listener for a given name receives every emit. Payloads are JSON objects whose schema is defined by the emitting site in the core.

Command domains

Commands live under ten domain modules. Counts shift between releases; the current truth is the registered generate_handler! list and the command modules in source.

DomainApprox. countCovers
interfaces~30AutoInterface, LoRa/RNode, RNode TCP endpoint setup, BLE RNode scan, serial discovery, TCP client/server, Backbone client/server, notifications, transport mode, connection history.
system~18Lifecycle, status, restart, app metadata, reset/clear actions.
network~20Announces, paths, Offline Inbox/propagation nodes, blackholes, hub interfaces, network log state and level.
messaging~14Send, list, mark-read, attachments, conversation CRUD.
identity~12Identity create/import/export/activate/switch, hashes, display name.
ble~10Bluetooth Peer, BLE RNode discovery, pairing prompts, cancellation, disconnects.
contacts~9Address book, trust state, custom names, hash lookup.
games~8LRGP game sessions, action submission, session deletion.
voice7Experimental LXST voice surface — service start/stop, status, call/answer/reject/hangup. Only compiled in when the lxst-voice Cargo feature is on; see rsLXST.
peers~1Live peer roster snapshot (mostly event-driven).

For the full per-command signature and argument schema, inspect the source with rg -n '#\[tauri::command\]|generate_handler!' crates/ratspeak-tauri/src/commands src-tauri/src/lib.rs in the Ratspeak repo.

Event vocabulary

Names the core broadcasts; subscribe to whichever your integration cares about.

EventPayload
system_statusCoarse lifecycle marker, for example ready, stopping, or stopped.
lxmf_identityActive identity hash and display name when loaded or switched.
contacts_updateFull contact list snapshot after any contact mutation.
conversations_updateConversation roster — last message, unread, peer hash.
lxmf_messageA single inbound or outbound LXMF message in canonical form.
lxmf_stepPer-message send or attachment progress, including validation errors.
unread_totalAggregate unread count across all conversations.
stats_updateTransport, link, and traffic counters; see Notes for cadence.
peers_updatedLive peer mesh roster snapshot.
all_game_sessionsLRGP session list after any session-state change.
node_operation_statusInterface add/remove/update progress for long-running network operations.
propagation_sync_resultPropagation-node sync completion or failure.
voice_call_updateActive LXST call lifecycle: status changes, profile, audio side state. Only emitted when the lxst-voice Cargo feature is on.
voice_incoming_callA new incoming LXST call that passed the contacts-only/zero-hop policy gate.

Other narrower events exist (identity_reset, identity_switched, auto_announce_updated, propagation_update, ble_peer_status_update, hub_interfaces_update, transport_mode_updated, etc.). Treat them as live; check the source if you depend on one.

Error model

Every command returns AppResult<T>. Tauri serializes the error variant directly, so on the JS side a rejected invoke Promise yields:

{ "code": "not_found", "message": "no contact for hash a1b2c3d4" }

Standard codes in use:

  • bad_request — argument validation failure
  • unauthorized / forbidden — capability gate refused the call
  • not_found — referenced row, hash, or session does not exist
  • conflict — state precondition violated (e.g. duplicate identity)
  • service_unavailable — RNS or transport not ready yet
  • database_unavailable — SQLite pool exhausted or migration mid-flight
  • lxmf_not_initialized — LXMF stack not up; retry after system_status reports ready
  • internal_error — unexpected; the message is human-readable, the cause is logged

Treat unknown codes as fatal for that call but not for the session.

Notes for integrators

Subscribe, don't poll. Every domain that matters emits when state changes. Polling commands in a loop wastes IPC bandwidth and races the broadcast.

Stats are the exception. The core emits stats_update on a 2.5-second cadence. Listen for that event instead of inventing a polling command.

Wait for readiness. On cold start, RNS comes up before LXMF. Calls into the messaging or identity domain before LXMF is ready return lxmf_not_initialized or service_unavailable. Wait for system_status to report ready, or retry with backoff after setup.

Identity is implicit. Commands operate against the currently loaded identity — there is no per-call identity argument. Listen for identity_switched to know when that context changes underneath you.

Attachments. The app exposes its current message, attachment, efficient-resource, and Offline Inbox/propagation-node transfer limits through api_lxmf_limits. Do not hard-code UI limits; they depend on the protocol resource ceiling and, for Offline Inbox delivery, the selected propagation node's advertised transfer limit.

Threading. Commands are async on a Tokio runtime; long work does not block the WebView. Events are emitted from whatever task produced them, so handlers must be reentrant.

Versioning. The IPC surface is not a stable public API. Pin to a Ratspeak release when shipping integrations and re-verify against the source on upgrade.