# Fluxer RPC Server Architecture: How the Desktop App Handles Internal Communication

> Explore the Fluxer RPC Server architecture. Learn how the desktop app secures renderer-to-main communication using an internal HTTP-based RPC server for a local-only API.

- Repository: [Fluxer/fluxer](https://github.com/fluxerapp/fluxer)
- Tags: architecture
- Published: 2026-03-17

---

**Fluxer's desktop application runs an internal HTTP-based RPC server inside the Electron main process that exposes a local-only API on loopback addresses for secure renderer-to-main communication.**

The **architecture of Fluxer's RPC server** is designed as a lightweight, security-first intermediary that allows the renderer process to control application behavior—such as window focusing and navigation—without exposing external network surfaces. According to the `fluxerapp/fluxer` source code, this system is implemented entirely within the Electron main process using Node.js built-in `http` modules, wrapped with strict origin validation and CORS handling.

## Core Components of the RPC Server

The RPC server implementation in [`fluxer_desktop/src/main/RpcServer.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/RpcServer.tsx) consists of several tightly integrated layers that handle networking, security, and request dispatch.

### HTTP Server and Port Binding

At the foundation, the server uses Node.js `http.createServer` to listen exclusively on `127.0.0.1`. The port selection is channel-dependent:

- **Stable builds**: Port `21863`
- **Canary builds**: Port `21864`

This configuration is determined by the `BUILD_CHANNEL` environment variable at runtime, ensuring that stable and development versions do not conflict when running simultaneously on the same machine.

### Security and Origin Validation

Security is enforced at the connection level before any request body is parsed. The server implements a multi-layer validation strategy in the `requestHandler` function (lines 91-100):

1. **Address validation**: Rejects any connection not originating from `127.0.0.1` or IPv6 loopback
2. **Origin checking**: Validates the `Origin` header against an allowlist containing `STABLE_APP_URL`, `CANARY_APP_URL`, and any custom URL defined in `DesktopConfig`
3. **Referer verification**: Ensures the `Referer` header starts with the validated `Origin` when both are present

CORS headers are injected only after successful origin validation, with pre-flight `OPTIONS` requests handled separately in the `handleCors` function (lines 95-115).

### Request Routing and Endpoints

The server exposes three primary endpoints via a path-based switch statement (lines 110-122):

- **`/health`**: Returns application metadata including version, build channel, and platform
- **`/navigate`**: Accepts a JSON payload with `{ path: string }`, forwards a `'rpc-navigate'` IPC event to the renderer process, and activates the window via `showWindow()`
- **`/focus`**: Brings the main window to the foreground without requiring parameters

Request bodies are limited to 1 MiB and parsed as JSON only for `POST` requests in the `parseBody` helper (lines 20-43). Malformed payloads return `null` and trigger early rejection.

### Lifecycle Management

Two async functions manage the server state:

- `startRpcServer()`: Creates the HTTP server instance if not already running and begins listening on the channel-specific port
- `stopRpcServer()`: Gracefully closes the listening socket and cleans up resources

Both return `Promise<void>` to support async/await patterns in the Electron lifecycle.

## Request Flow and Security Model

Understanding how requests move through the system clarifies the trust boundaries between the renderer and main process.

When the Electron app initializes in [`src/main/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/src/main/index.tsx), it invokes `startRpcServer()` after the `app.whenReady()` event resolves. Incoming connections undergo immediate IP filtering at the TCP level. Valid loopback connections proceed to header inspection, where the `Origin` and `Referer` headers are compared against the configured URL allowlist.

Upon passing security checks, the request body is parsed and routed to the appropriate handler. Navigation and focus operations interact with the window manager through `getMainWindow()` and `showWindow()` utilities from [`src/main/Window.tsx`](https://github.com/fluxerapp/fluxer/blob/main/src/main/Window.tsx). All responses follow a consistent JSON envelope format: `{ success: boolean, data?: any, error?: string }`.

Uncaught exceptions in any handler are logged via `electron-log` and returned to the client as HTTP 500 errors with JSON error bodies, preventing process crashes from exposing stack traces while maintaining diagnostic capabilities.

## Implementation Examples

### Starting the Server in the Main Process

The RPC server integrates with the Electron application lifecycle in the main entry point:

```typescript
import { app } from 'electron';
import { startRpcServer, stopRpcServer } from '@electron/main/RpcServer';

app.whenReady()
  .then(() => startRpcServer())
  .catch(console.error);

app.on('quit', () => {
  void stopRpcServer();
});

```

This pattern ensures the server starts only after Electron's internal initialization completes and shuts down cleanly when the application exits.

### Making RPC Calls from the Renderer

To navigate programmatically from a renderer context:

```typescript
async function navigateToPath(path: string) {
  const port = 21863; // 21864 for canary builds
  const response = await fetch(`http://127.0.0.1:${port}/navigate`, {
    method: 'POST',
    headers: { 
      'Content-Type': 'application/json',
      'Origin': window.location.origin 
    },
    body: JSON.stringify({ path })
  });
  
  const result = await response.json();
  if (!result.success) throw new Error(result.error);
  return result.data;
}

```

The request must include a valid `Origin` header matching the allowlist configured in `DesktopConfig`.

### Health Check Implementation

Polling the server status requires no authentication beyond loopback access:

```typescript
fetch('http://127.0.0.1:21863/health')
  .then(res => res.json())
  .then(({ data }) => {
    console.log(`Server healthy: ${data.status}, Channel: ${data.channel}`);
  });

```

Example response:

```json
{
  "success": true,
  "data": {
    "status": "ok",
    "channel": "stable",
    "version": "1.2.3",
    "platform": "darwin"
  }
}

```

## Summary

- **Fluxer implements a localhost-only HTTP RPC server** using Node.js `http` in the Electron main process, binding exclusively to `127.0.0.1` on ports 21863 (stable) or 21864 (canary).
- **Security relies on multi-layer validation** including IP filtering, Origin/Referer header checking against configured URLs, and strict CORS policies.
- **Three primary endpoints** (`/health`, `/navigate`, `/focus`) expose application control capabilities, with navigation forwarding IPC messages to the renderer.
- **Lifecycle management** occurs through `startRpcServer()` and `stopRpcServer()` in [`src/main/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/src/main/index.tsx), aligning with Electron's ready and quit events.
- **Request parsing is strictly limited** to 1 MiB JSON payloads, with all errors returned as structured JSON responses rather than thrown exceptions.

## Frequently Asked Questions

### Why does Fluxer use HTTP instead of Electron's built-in IPC for RPC?

While Electron provides robust IPC mechanisms, the HTTP-based RPC server in [`src/main/RpcServer.tsx`](https://github.com/fluxerapp/fluxer/blob/main/src/main/RpcServer.tsx) allows external tooling and browser extensions running on localhost to interact with the desktop application directly. This architecture enables web-based integrations to control the Fluxer desktop client using standard fetch APIs while maintaining security through origin validation and loopback restriction.

### How does the server prevent unauthorized websites from accessing the RPC endpoints?

The server implements defense in depth through multiple validation layers defined in lines 91-100 of [`RpcServer.tsx`](https://github.com/fluxerapp/fluxer/blob/main/RpcServer.tsx). It rejects any connection not originating from `127.0.0.1`, validates the `Origin` header against a strict allowlist (`STABLE_APP_URL`, `CANARY_APP_URL`, and custom configured URLs), and verifies that the `Referer` header matches the origin. Combined with CORS headers that only reflect validated origins, this prevents cross-site request forgery from remote websites.

### What happens if the RPC server encounters an error during request processing?

All errors are caught in the `requestHandler` try-catch block (lines 124-126) and logged using `electron-log` for debugging purposes. The client receives an HTTP 500 response with a JSON error envelope containing `{ success: false, error: string }`, ensuring the server remains stable even when handlers throw exceptions or receive malformed payloads.

### Can the RPC server port be configured or changed at runtime?

The port is determined by the `BUILD_CHANNEL` environment variable at startup, selecting between 21863 (stable) and 21864 (canary) as implemented in lines 28-38 of [`RpcServer.tsx`](https://github.com/fluxerapp/fluxer/blob/main/RpcServer.tsx). While the port constants are compiled into the application based on the build configuration, the allowed origins can be extended at runtime through the desktop configuration system in [`src/common/DesktopConfig.tsx`](https://github.com/fluxerapp/fluxer/blob/main/src/common/DesktopConfig.tsx), enabling custom development URLs without recompiling.