Adding IPC Handlers
How to add a custom IPC handler in C++ and expose it to the React frontend.
The built-in IPC handlers cover config operations, but you can add your own handlers for anything custom — authentication, feature toggling, fetching live data from the game, etc. Adding a new handler requires changes in three places: the C++ backend to handle the message, the Rust bridge to expose it as a Tauri command, and the React frontend to call it.
Example: Authentication
This example adds a validate_key handler that takes a license key from the UI, validates it in C++, and returns whether it's valid.
1. Register the Handler in C++
You can either add your handler directly inside register_default_handlers in default_handlers.cpp, or create a separate file like auth_handlers.cpp and call it from main.cpp — either approach works fine. The important thing is that all handlers are registered before g_server->start() is called.
Option A — Add to default_handlers.cpp:
g_server->on(
"validate_key",
[](const nlohmann::json &request)
{
std::string key = request.value("key", "");
if (key.empty())
{
return nlohmann::json{{"success", false}, {"error", "No key provided"}};
}
const bool valid = auth::validate_license_key(key);
return nlohmann::json{{"success", valid}};
});Option B — Create a separate file:
// src-cheat/src/ipc/auth_handlers.cpp
#include "auth_handlers.h"
#include "server.h"
namespace ipc
{
auto register_auth_handlers() -> void
{
g_server->on(
"validate_key",
[](const nlohmann::json &request)
{
std::string key = request.value("key", "");
if (key.empty())
{
return nlohmann::json{{"success", false}, {"error", "No key provided"}};
}
const bool valid = auth::validate_license_key(key);
return nlohmann::json{{"success", valid}};
});
}
} // namespace ipcThen call it in main.cpp alongside register_default_handlers:
ipc::register_default_handlers();
ipc::register_auth_handlers();The handler receives a JSON object and must return a JSON object. The response is automatically sent back with _response appended to the type name, so validate_key becomes validate_key_response.
2. Create the Rust Handler File
Create a new file at src-tauri/src/ipc/handlers/auth.rs:
use serde_json::json;
use tauri::State;
use crate::ipc::IpcConnection;
#[tauri::command]
pub async fn validate_key(
key: String,
conn: State<'_, IpcConnection>,
) -> Result<bool, String> {
let data = conn
.inner()
.request_async("validate_key", json!({ "key": key }))
.await?;
if let Some(err) = data.get("error").and_then(|v| v.as_str()) {
return Err(err.to_string());
}
Ok(data
.get("success")
.and_then(|v| v.as_bool())
.unwrap_or(false))
}3. Export the Handler
Open src-tauri/src/ipc/handlers/mod.rs and add your new module:
pub mod config;
pub mod auth;4. Register the Tauri Command
Open src-tauri/src/lib.rs and add your command to the invoke_handler:
.invoke_handler(tauri::generate_handler![
ipc::ipc_ping,
ipc::handlers::config::config_file_list,
ipc::handlers::config::config_file_load,
ipc::handlers::config::config_file_save,
ipc::handlers::config::config_file_delete,
ipc::handlers::config::get_config,
ipc::handlers::config::set_config,
ipc::handlers::config::set_all_configs,
ipc::handlers::auth::validate_key, // add this
])5. Call it from the React Frontend
Add the invoke call to src-web/ipc.ts:
export async function validateKey(key: string): Promise<boolean> {
return invoke<boolean>("validate_key", { key });
}Then use it anywhere in your components:
import { validateKey } from "@/ipc";
import { useState } from "react";
const [key, setKey] = useState("");
const [error, setError] = useState<string | null>(null);
const handleSubmit = async () => {
setError(null);
try {
const valid = await validateKey(key);
if (!valid) {
setError("Invalid license key");
}
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
}
};Summary
The pattern for any new handler is always the same steps:
- Register the handler in C++ with
g_server->on(...)either indefault_handlers.cppor a new file - Create a Rust file in
src-tauri/src/ipc/handlers/that callsrequest_asyncwith the same type name - Export it in
handlers/mod.rs - Register it in
lib.rsinsideinvoke_handler - Add an
invokewrapper insrc/ipc.tsand call it from your components