- Version: API 0.5.2
- .NET: 10.0.302 SDK
- Target: win-x64 (Windows 11)
The socket.io bridge between the Electron host and the .NET backend runs over
http://localhost:<electronPort>, but the .NET client never opts out of the system
proxy. On Windows, as soon as a proxy is configured without <local> in the bypass
list, the engine.io handshake is sent to the proxy instead of the loopback socket
server, ConnectAsync never completes, and the app never opens a window.
Steps to Reproduce:
- Windows → Settings → Network & Internet → Proxy → Manual proxy setup: enable it,
set any address (e.g. 192.168.1.56:80), and leave the exception list empty
(i.e. do not tick "Don't use the proxy server for local addresses").
- Start any Electron.NET app in Electron-first mode.
- Electron logs
Electron Socket: listening on port <p> at 127.0.0.1, the backend
process starts, but BridgeConnector connected! is never printed and no window
is created. The app hangs on the splash screen (or exits silently if none).
- Re-tick "Don't use the proxy server for local addresses" (or disable the proxy)
and restart → the app starts normally.
Cause
SocketBridgeService builds the loopback URL:
// src/ElectronNET.API/Runtime/Services/SocketBridge/SocketBridgeService.cs:22
this.socketUrl = $"http://localhost:{this.socketPort}";
and SocketIOConnection passes options that never set Proxy:
// src/ElectronNET.API/Bridge/SocketIOConnection.cs:19-31
var opts = string.IsNullOrEmpty(authorization) ? new SocketIOOptions() : new SocketIOOptions
{
ExtraHeaders = new Dictionary<string, string> { ["authorization"] = authorization },
};
_socket = new SocketIO(uri, opts);
SocketIOClient 3.1.2 defaults are Transport = Polling, AutoUpgrade = true,
Proxy = null. Both IHttpClient.SetProxy and IClientWebSocket.SetProxy are only
invoked when Options.Proxy is non-null, so:
- the polling handshake goes through
HttpClientHandler with UseProxy = true, Proxy = null → falls back to HttpClient.DefaultProxy (WinINET settings);
- the websocket upgrade goes through
ClientWebSocket with the same default.
.NET exempts loopback only when the Windows override list contains <local>:
ProxyOverride empty (BypassOnLocal=false) -> http://192.168.1.56/
with <local> (BypassOnLocal=true) -> http://127.0.0.1:53123/
Chromium is unaffected because it has an implicit proxy bypass for loopback, so the
BrowserWindow side would work — the failure is entirely on the .NET client leg.
Suggested fix
Force a direct connection for the bridge only, so that application traffic keeps
honouring the user's proxy:
var opts = new SocketIOOptions
{
// The bridge is a loopback IPC channel, never real network traffic.
Proxy = DirectProxy.Instance,
};
private sealed class DirectProxy : IWebProxy
{
public static readonly DirectProxy Instance = new DirectProxy();
public ICredentials Credentials { get; set; }
public Uri GetProxy(Uri destination) => destination;
public bool IsBypassed(Uri host) => true;
}
IsBypassed => true covers both the polling transport and the websocket upgrade.
Setting HttpClient.DefaultProxy globally would not be acceptable, since it would
also disable the proxy for the host application's own outbound requests.
Chromium is unaffected because it has an implicit proxy bypass for loopback, so the
`BrowserWindow` side would work — the failure is entirely on the .NET client leg.
**Suggested fix**
Force a direct connection for the bridge only, so that application traffic keeps
honouring the user's proxy:
```csharp
var opts = new SocketIOOptions
{
// The bridge is a loopback IPC channel, never real network traffic.
Proxy = DirectProxy.Instance,
};
private sealed class DirectProxy : IWebProxy
{
public static readonly DirectProxy Instance = new DirectProxy();
public ICredentials Credentials { get; set; }
public Uri GetProxy(Uri destination) => destination;
public bool IsBypassed(Uri host) => true;
}
IsBypassed => true covers both the polling transport and the websocket upgrade.
Setting HttpClient.DefaultProxy globally would not be acceptable, since it would
also disable the proxy for the host application's own outbound requests.
The socket.io bridge between the Electron host and the .NET backend runs over
http://localhost:<electronPort>, but the .NET client never opts out of the systemproxy. On Windows, as soon as a proxy is configured without
<local>in the bypasslist, the engine.io handshake is sent to the proxy instead of the loopback socket
server,
ConnectAsyncnever completes, and the app never opens a window.Steps to Reproduce:
set any address (e.g.
192.168.1.56:80), and leave the exception list empty(i.e. do not tick "Don't use the proxy server for local addresses").
Electron Socket: listening on port <p> at 127.0.0.1, the backendprocess starts, but
BridgeConnector connected!is never printed and no windowis created. The app hangs on the splash screen (or exits silently if none).
and restart → the app starts normally.
Cause
SocketBridgeServicebuilds the loopback URL:and
SocketIOConnectionpasses options that never setProxy:SocketIOClient 3.1.2 defaults are
Transport = Polling,AutoUpgrade = true,Proxy = null. BothIHttpClient.SetProxyandIClientWebSocket.SetProxyare onlyinvoked when
Options.Proxyis non-null, so:HttpClientHandlerwithUseProxy = true, Proxy = null→ falls back toHttpClient.DefaultProxy(WinINET settings);ClientWebSocketwith the same default..NET exempts loopback only when the Windows override list contains
<local>:Chromium is unaffected because it has an implicit proxy bypass for loopback, so the
BrowserWindowside would work — the failure is entirely on the .NET client leg.Suggested fix
Force a direct connection for the bridge only, so that application traffic keeps
honouring the user's proxy:
IsBypassed => truecovers both the polling transport and the websocket upgrade.Setting
HttpClient.DefaultProxyglobally would not be acceptable, since it wouldalso disable the proxy for the host application's own outbound requests.
IsBypassed => truecovers both the polling transport and the websocket upgrade.Setting
HttpClient.DefaultProxyglobally would not be acceptable, since it wouldalso disable the proxy for the host application's own outbound requests.