Web Overlay

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 ipc

Then 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:

  1. Register the handler in C++ with g_server->on(...) either in default_handlers.cpp or a new file
  2. Create a Rust file in src-tauri/src/ipc/handlers/ that calls request_async with the same type name
  3. Export it in handlers/mod.rs
  4. Register it in lib.rs inside invoke_handler
  5. Add an invoke wrapper in src/ipc.ts and call it from your components

On this page