Skip to content

Electron app does not start correctly in case of proxies #1105

Description

@QJonny
  • 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:

  1. 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").
  2. Start any Electron.NET app in Electron-first mode.
  3. 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).
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions