# How the Remote Web Panel Works in Wand‑Enhancer: LAN Control Architecture

> Discover how Wand-Enhancer's Remote Web Panel uses LAN control architecture with an HTTP server and WebSocket to manage games and execute trainer commands from any local device.

- Repository: [k1tbyte/Wand-Enhancer](https://github.com/k1tbyte/Wand-Enhancer)
- Tags: architecture
- Published: 2026-09-01

---

**The Remote Web Panel in Wand‑Enhancer is a LAN‑only control interface that combines an HTTP server listening on TCP port 3223, a WebSocket protocol defined in [`web-contract.json`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-contract.json), and an Electron IPC bridge to synchronize installed games and execute remote trainer commands from any device on your local network.**

The **Remote Web Panel** in the k1tbyte/Wand‑Enhancer repository enables users to control the Wand application from any device on the same local network, such as a smartphone or tablet. This feature operates through a sophisticated three‑layer architecture that bridges the .NET patcher, Electron renderer process, and a web‑based UI. Understanding how this **Remote Web Panel** functions requires examining its HTTP server implementation, WebSocket contract, and the bridge scripts that facilitate inter‑process communication.

## Three‑Layer Architecture Overview

The implementation is divided into three distinct layers that cooperate at runtime:

- **HTTP Server & WebSocket Bridge** – An Express‑style server started by the .NET patcher that listens on TCP port 3223 and serves the compiled UI from `web-panel/dist/renderer-scripts`. It manages WebSocket connections following the contract in [`web-panel/protocol/web-contract.json`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/protocol/web-contract.json).

- **Electron ↔ Wand Bridge** – A TypeScript bundle (`web-panel/dist/bridge.cjs`) running inside Wand’s renderer process that uses `electron.ipcRenderer` to expose IPC channels for bidirectional communication between the remote UI and Wand’s internal services.

- **Remote Panel Scripts** – JavaScript modules bundled into `web-panel/dist/renderer-scripts` that implement game synchronization, trainer status publishing, remote command handling, and UI cleanup logic.

## HTTP Server and WebSocket Contract

### Server Initialization in MainWindowVm.cs

The server lifecycle originates in [`WandEnhancer/View/MainWindow/MainWindowVm.cs`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/WandEnhancer/View/MainWindow/MainWindowVm.cs). When the patcher initializes, it starts a lightweight HTTP server that binds to the LAN address on **TCP port 3223** (the default remote-panel port). This server serves static files produced by the Vite build located in `web-panel/dist/renderer-scripts`, making the UI accessible via any browser on the local network.

Simultaneously, the server opens a WebSocket endpoint that adheres strictly to the protocol defined in [`web-panel/protocol/web-contract.json`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/protocol/web-contract.json). The TypeScript type definitions in [`web-panel/protocol/messages.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/protocol/messages.ts) enforce message structures across the bridge.

### Message Protocol Structure

The WebSocket contract supports several distinct message types that facilitate real‑time state synchronization:

- **`hello`** – Transmits the initial snapshot containing the complete list of installed applications and the current trainer state upon client connection.
- **`remote_command`** – Carries execution requests from the remote client to launch or stop specific trainers, identified by `gameId`.
- **`game_status`** – Pushes real‑time lifecycle updates including `game-launched`, `game-ended`, and trainer visibility changes.

## Electron Bridge and IPC Communication

### Bridge Compilation and Injection

The bridge is compiled from TypeScript source using `pnpm run build:bridge` and output to `web-panel/dist/bridge.cjs`. When injected into Wand’s renderer process, it resolves critical services—including the installed‑apps service, trainer service, and store—via the Aurelia dependency injection container and Webpack’s runtime ([`runtime.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/runtime.js)).

The bridge installs a polling timer controlled by `SYNC_INTERVAL_MS` (approximately 5 seconds) that periodically triggers `syncInstalledApps()` to rebuild the state snapshot. All error handling routes through `WandEnhancer.log` to ensure uncaught exceptions never crash the host application.

### IPC Channel Bindings

The bridge establishes three primary IPC channels using `electron.ipcRenderer`:

- **`BIND_CHANNEL`** – Registers a "set‑value" handler in Wand that the remote UI can invoke to modify application state.
- **`SYNC_CHANNEL`** – Receives the snapshot constructed by `syncInstalledApps()` and forwards it over the WebSocket to connected clients.
- **`COMMAND_REQUEST_CHANNEL`** – Intercepts remote execution requests and routes them to the trainer service via [`remote-commands.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/remote-commands.js).

The core entry point `installInstalledAppsSync(WandEnhancer)` in [`web-panel/bridge/scripts/default/installed-apps-sync/index.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/bridge/scripts/default/installed-apps-sync/index.js) orchestrates the bootstrap sequence. It implements a `GLOBAL_FLAG` check (`globalThis.__wandInstalledAppsSync`) to ensure the bridge installs exactly once, even if the injection script runs multiple times.

## Remote Panel Scripts and Logic

### Installed Apps Synchronization

The script [`web-panel/bridge/scripts/default/installed-apps-sync/installed-data.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/bridge/scripts/default/installed-apps-sync/installed-data.js) collects raw installed applications from `installedAppsService.installedApps` and resolves missing metadata via Wand’s CDN. The `buildSnapshot` function aggregates this data with the Redux store’s `myGames` state to produce a comprehensive payload that includes diagnostic counts of raw versus catalogued games.

### Game Status Monitoring

Defined in [`web-panel/bridge/scripts/default/installed-apps-sync/game-status.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/bridge/scripts/default/installed-apps-sync/game-status.js), this module subscribes to Wand’s internal `wand-remote-game-status` channel. It translates native game lifecycle events into WebSocket `game_status` messages, ensuring the remote panel reflects trainer launch and termination in real time.

### Remote Command Execution

The [`web-panel/bridge/scripts/default/installed-apps-sync/remote-commands.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/bridge/scripts/default/installed-apps-sync/remote-commands.js) module handles `remote_command` messages. When receiving a "play" request, it constructs a trainer launch request using `trainerLaunchRequestCtor`, instantiates it with the target `gameId`, and invokes `trainerService.launch(...)` to execute the trainer within Wand.

### UI Cleanup and Artwork

Two scripts handle presentation logic:

- **[`remote-popup-cleanup.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/remote-popup-cleanup.js)** – Executes on the UI side to rewrite tooltip URLs and QR code links. It hides the Pro‑onboarding card while preserving Wand’s existing QR renderer functionality.
- **[`artwork.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/artwork.js)** – Selects optimal game artwork by preferring CDN URLs in the format `https://api-cdn.wemod.com/steam_community/<steamAppId>/client_icon/96.webp`.

## End‑to‑End Communication Flow

1. **Startup Phase** – The patcher calls `StartRemotePanel()` in [`MainWindowVm.cs`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/MainWindowVm.cs), initializing the HTTP server on port 3223. The Vite build serves the static UI bundle, and the bridge script injects into Wand’s renderer via `installInstalledAppsSync`.

2. **Client Handshake** – A mobile device navigates to `http://<pc-ip>:3223`. The WebSocket connection establishes, and the client sends a `hello` request. The bridge responds immediately with the current `installed_apps` snapshot and `game_status`.

3. **Synchronization Loop** – Every `SYNC_INTERVAL_MS` (≈5 seconds), the bridge polls Wand’s services, rebuilds the snapshot through `buildSnapshot`, and pushes updates via the `SYNC_CHANNEL` WebSocket frame.

4. **Command Execution** – When the user taps "Play" on the remote device, the client emits a `remote_command` message. The bridge receives this on `COMMAND_REQUEST_CHANNEL`, creates the proper trainer launch object, and calls `trainerService.launch(...)`. Lifecycle events propagate back through `game_status` messages.

5. **UI Maintenance** – [`remote-popup-cleanup.js`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/remote-popup-cleanup.js) continuously sanitizes the interface, removing Pro upsell elements and ensuring QR codes render using Wand’s native utilities.

All communication remains confined to the local area network; no authentication tokens or personal data transmit externally. The lightweight design maintains compatibility with the ASAR‑patch pipeline.

## Implementation Code Examples

### Starting the HTTP Server

```csharp
// In WandEnhancer/View/MainWindow/MainWindowVm.cs
private void StartRemotePanel()
{
    // Starts the LAN HTTP/WebSocket server on port 3223.
    RemotePanel.Start(port: 3223);
}

```

### Injecting the Bridge Script

```javascript
// Executed inside Wand's renderer process
import { installInstalledAppsSync } from "./bridge/scripts/default/installed-apps-sync/index.js";

if (typeof globalThis.__wandInstalledAppsSync === "undefined") {
  globalThis.__wandInstalledAppsSync = true;
  installInstalledAppsSync(WandEnhancer);
}

```

### Processing Remote Play Commands

```javascript
// web-panel/bridge/scripts/default/installed-apps-sync/remote-commands.js
export async function handleRemoteCommandRequest(state, _event, request) {
  if (request.type !== "play") return;

  const trainerCtor = state.trainerLaunchRequestCtor;
  const launchRequest = new trainerCtor(request.gameId);
  await state.trainerService.launch(launchRequest);
  state.log("info", "Trainer launched remotely", request.gameId);
}

```

### Building the Application Snapshot

```javascript
// web-panel/bridge/scripts/default/installed-apps-sync/installed-data.js
export function buildSnapshot(state) {
  const apps = state.installedAppsService?.installedApps ?? [];
  const catalogGames = state.storeRef?.getState()?.myGames ?? [];
  return {
    apps,
    catalogGames,
    diagnostics: {
      rawInstalledApps: apps.length,
      catalogGames,
    },
  };
}

```

## Summary

- The **Remote Web Panel** operates on **TCP port 3223** and is strictly LAN‑accessible, requiring no external authentication.
- The architecture consists of three layers: an HTTP/WebSocket server in [`MainWindowVm.cs`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/MainWindowVm.cs), an Electron IPC bridge compiled to `bridge.cjs`, and specialized sync scripts in `web-panel/bridge/scripts/`.
- **IPC channels** `BIND_CHANNEL`, `SYNC_CHANNEL`, and `COMMAND_REQUEST_CHANNEL` manage the flow of control messages, state snapshots, and execution commands.
- The bridge uses a **5‑second polling interval** (`SYNC_INTERVAL_MS`) to keep remote clients synchronized with Wand’s installed games and trainer status.
- Remote commands execute through `trainerService.launch()` after constructing request objects via `trainerLaunchRequestCtor`, with full error isolation via `WandEnhancer.log`.

## Frequently Asked Questions

### What port does the Remote Web Panel use by default?

The Remote Web Panel binds to **TCP port 3223** by default. This is hardcoded in the server initialization within [`WandEnhancer/View/MainWindow/MainWindowVm.cs`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/WandEnhancer/View/MainWindow/MainWindowVm.cs), though the underlying implementation may support configuration changes if modified in the source.

### Is the Remote Web Panel accessible from the internet?

No, the Remote Web Panel is intentionally **LAN‑only**. The HTTP server binds to the local network interface (127.0.0.1 or the machine’s local IP), and the implementation does not include authentication mechanisms or external routing capabilities. It is designed specifically for controlling Wand from devices on the same physical or Wi‑Fi network.

### How does the bridge prevent duplicate script execution?

The bridge utilizes a **global flag pattern** via `globalThis.__wandInstalledAppsSync`. Before installation, the script checks if this property exists on the global object. If undefined, it sets the flag to `true` and proceeds with `installInstalledAppsSync(WandEnhancer)`. If the flag exists, the installation skips, preventing duplicate IPC channel registrations and redundant polling timers.

### How often does the panel synchronize game data?

The bridge maintains a synchronization loop using `SYNC_INTERVAL_MS`, which defaults to approximately **5 seconds**. This timer triggers `syncInstalledApps()` to poll Wand’s services and push updated snapshots through the WebSocket, ensuring the remote UI reflects trainer launches, game terminations, and newly installed applications with minimal latency.