HyPrism follows a Console + IPC + React SPA architecture pattern:
┌─────────────────────────────────────────────────────┐
│ .NET Console App (Program.cs) │
│ ├── Bootstrapper.cs (DI container) │
│ ├── Services/ (business logic) │
│ └── IpcService.cs (IPC channel registry) │
│ ↕ Electron.NET socket bridge │
│ ┌─────────────────────────────────────────────┐ │
│ │ Electron Main Process │ │
│ │ └── BrowserWindow (frameless) │ │
│ │ └── preload.js (contextBridge) │ │
│ │ ↕ ipcRenderer │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ React SPA │ │ │
│ │ │ ├── App.tsx (routing) │ │ │
│ │ │ ├── pages/ (views) │ │ │
│ │ │ ├── components/ (shared) │ │ │
│ │ │ └── lib/ipc.ts (generated) │ │ │
│ │ └─────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Program.Main()initializes Serilog logger- Installs
ElectronLogInterceptoronConsole.Out/Console.Error Bootstrapper.Initialize()builds the DI containerElectronNetRuntime.RuntimeController.Start()spawns Electron processElectronBootstrap()creates a framelessBrowserWindowloadingfile://wwwroot/index.htmlIpcService.RegisterAll()registers all IPC channel handlers- React SPA mounts, fetches data via typed IPC calls
All frontend ↔ backend communication uses named IPC channels:
Channel naming: hyprism:{domain}:{action}
Examples: hyprism:game:launch
hyprism:settings:get
hyprism:i18n:set
| Type | Direction | Pattern |
|---|---|---|
| send | React → .NET (fire-and-forget) | send(channel, data) |
| invoke | React → .NET → React (request/reply) | invoke(channel, data) → waits for :reply |
| event | .NET → React (push) | on(channel, callback) |
contextIsolation: true— renderer has no access to Node.jsnodeIntegration: false— norequire()in rendererpreload.jsexposes onlywindow.electron.ipcRendererviacontextBridge
The IPC bridge uses HTTP socket for .NET ↔ Electron communication.
On Windows, the socket binds to 0.0.0.0 instead of 127.0.0.1 to bypass VPN interception.
Security: All connections are filtered — only loopback addresses are accepted:
127.0.0.1(IPv4 loopback)::1(IPv6 loopback)::ffff:127.0.0.1(IPv6-mapped IPv4)
Override: Set HYPRISM_VPN_COMPAT=0 to force 127.0.0.1 binding.
The socket bridge is patched in .electron/custom_main.js before Electron.NET initializes:
// Windows defaults to 0.0.0.0, others use 127.0.0.1
const vpnCompatMode = vpnCompatEnv === '1' || (isWindows && vpnCompatEnv !== '0');
// Connection filtering
if (!isLoopback) {
socket.destroy(); // Reject non-loopback
}All services are registered as singletons in Bootstrapper.cs:
var services = new ServiceCollection();
services.AddSingleton<ConfigService>();
services.AddSingleton<IpcService>();
// ... etc
return services.BuildServiceProvider();IpcService receives all other services through constructor injection and acts as the central bridge between React and .NET.
Electron.NET emits unstructured messages to stdout/stderr (e.g. [StartCore]:, || ...). HyPrism intercepts these via ElectronLogInterceptor (a custom TextWriter installed on Console.Out/Console.Error) and routes them through the structured Logger:
- Framework messages →
Logger.Info("Electron", ...) - Debug messages (
[StartCore],BridgeConnector) →Logger.Debug("Electron", ...) - Error patterns (
ERROR:,crash) →Logger.Warning("Electron", ...) - Noise patterns (
GetVSyncParametersIfAvailable) → suppressed