Skip to content

Commit bbd1065

Browse files
committed
Merge branch 'epsitec-improvements' into feature/secure-connection
2 parents 0fa0abd + bfd51e6 commit bbd1065

127 files changed

Lines changed: 65232 additions & 1282 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/SignalR-Authentication-Guide.md

Lines changed: 583 additions & 0 deletions
Large diffs are not rendered by default.

docs/SignalR-Implementation-Summary.md

Lines changed: 450 additions & 0 deletions
Large diffs are not rendered by default.

docs/SignalR-Startup-Mode.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# SignalR-Based Startup Mode for Electron.NET
2+
3+
## Overview
4+
5+
This feature adds a new startup mode for Electron.NET where:
6+
- **.NET/ASP.NET Core starts first** and binds to port 0 (dynamic port)
7+
- **Kestrel picks an available port** automatically
8+
- **Electron process is launched** with the actual URL
9+
- **SignalR is used for communication** instead of socket.io
10+
- **Blazor Server apps** can coexist with Electron control
11+
12+
## Status
13+
14+
**Phases 1-5 Complete** - Infrastructure ready, basic functionality implemented
15+
⏸️ **Phase 6 Pending** - Full API integration, testing, and documentation
16+
17+
## How It Works
18+
19+
### Startup Sequence
20+
21+
1. ASP.NET Core application starts
22+
2. Kestrel binds to `http://localhost:0` (random available port)
23+
3. `RuntimeControllerAspNetDotnetFirstSignalR` captures the actual port via `IServerAddressesFeature`
24+
4. Electron process is launched with `--electronUrl=http://localhost:XXXXX`
25+
5. Electron's main.js detects SignalR mode (via `--dotnetpackedsignalr` or `--unpackeddotnetsignalr` flag)
26+
6. Electron connects to SignalR hub at `/electron-hub`
27+
7. Hub notifies runtime controller of successful connection
28+
8. Application transitions to "Ready" state
29+
9. `ElectronAppReady` callback is invoked
30+
31+
### Communication Flow
32+
33+
```
34+
.NET/Kestrel (Port 0) ←→ SignalR Hub (/electron-hub) ←→ Electron Process
35+
↓ ↓ ↓
36+
Blazor Server ElectronHub class SignalR Client
37+
(/_blazor hub) (API commands) (main.js + signalr-bridge.js)
38+
```
39+
40+
## Usage
41+
42+
### 1. Enable SignalR Mode
43+
44+
Set the environment variable:
45+
```bash
46+
ELECTRON_USE_SIGNALR=true
47+
```
48+
49+
Or in launchSettings.json:
50+
```json
51+
{
52+
"environmentVariables": {
53+
"ELECTRON_USE_SIGNALR": "true"
54+
}
55+
}
56+
```
57+
58+
### 2. Configure ASP.NET Core
59+
60+
In your `Program.cs`:
61+
62+
```csharp
63+
using ElectronNET.API;
64+
using ElectronNET.API.Entities;
65+
66+
var builder = WebApplication.CreateBuilder(args);
67+
68+
builder.Services.AddRazorPages();
69+
builder.Services.AddElectron();
70+
71+
builder.UseElectron(args, async () =>
72+
{
73+
var window = await Electron.WindowManager.CreateWindowAsync(
74+
new BrowserWindowOptions { Show = false });
75+
76+
window.OnReadyToShow += () => window.Show();
77+
});
78+
79+
var app = builder.Build();
80+
81+
// Configure middleware
82+
app.UseStaticFiles();
83+
app.UseRouting();
84+
85+
// Map the Electron SignalR hub
86+
app.MapElectronHub(); // ← Required for SignalR mode
87+
app.MapRazorPages();
88+
89+
app.Run();
90+
```
91+
92+
### 3. Run Your Application
93+
94+
Just press F5 in Visual Studio or run:
95+
```bash
96+
dotnet run
97+
```
98+
99+
The application will:
100+
- Automatically detect SignalR mode via environment variable
101+
- Bind Kestrel to port 0
102+
- Launch Electron with the correct URL
103+
- Establish SignalR connection
104+
105+
## Components
106+
107+
### .NET Side
108+
109+
- **`ElectronHub`** - SignalR hub at `/electron-hub`
110+
- **`SignalRFacade`** - Mimics `SocketIoFacade` interface for compatibility
111+
- **`RuntimeControllerAspNetDotnetFirstSignalR`** - Lifecycle management
112+
- **`StartupMethod.PackagedDotnetFirstSignalR`** - For packaged apps
113+
- **`StartupMethod.UnpackedDotnetFirstSignalR`** - For debugging
114+
115+
### Electron Side
116+
117+
- **`signalr-bridge.js`** - SignalR client wrapper
118+
- **`main.js`** - Detects SignalR mode and connects to hub
119+
- **`@microsoft/signalr`** npm package
120+
121+
## Key Features
122+
123+
**Dynamic Port Assignment** - No hardcoded ports, no conflicts
124+
**Blazor Server Compatible** - Separate hub endpoints (`/electron-hub` vs `/_blazor`)
125+
**Bidirectional Communication** - Both .NET→Electron and Electron→.NET
126+
**Hot Reload Support** - SignalR automatic reconnection
127+
**Multiple Instances** - Each instance gets its own port
128+
129+
## Current Limitations (Phase 6 Work Needed)
130+
131+
⚠️ **Electron API Integration** - Existing Electron APIs (WindowManager, Dialog, etc.) still use SocketIoFacade. Full integration requires:
132+
- Refactoring APIs to work with both facades, or
133+
- Creating an adapter pattern
134+
135+
⚠️ **Request-Response Pattern** - Current hub methods are one-way. Need to implement proper async request-response for API calls.
136+
137+
⚠️ **Event Routing** - Electron events need to be routed through SignalR back to .NET.
138+
139+
⚠️ **Testing** - Integration tests needed to validate end-to-end functionality.
140+
141+
## What's Implemented
142+
143+
### Phase 1: Core Infrastructure ✅
144+
- New `StartupMethod` enum values
145+
- `ElectronHub` SignalR hub
146+
- Hub endpoint registration
147+
148+
### Phase 2: Runtime Controller ✅
149+
- `RuntimeControllerAspNetDotnetFirstSignalR`
150+
- Port 0 binding logic
151+
- Electron launch with URL parameter
152+
- SignalR connection tracking
153+
154+
### Phase 3: Electron/Node.js Side ✅
155+
- `@microsoft/signalr` package integration
156+
- SignalR connection module
157+
- Startup mode detection
158+
- URL parameter handling
159+
160+
### Phase 4: API Bridge ✅ (Basic Structure)
161+
- `SignalRFacade` class
162+
- Event handler system
163+
- Hub connection integration
164+
165+
### Phase 5: Configuration ✅
166+
- Environment variable detection
167+
- Port 0 configuration
168+
- Automatic service registration
169+
170+
## Next Steps (Phase 6)
171+
172+
To fully utilize this feature, the following work is recommended:
173+
174+
1. **API Integration** - Make existing Electron APIs work with SignalR
175+
2. **Sample Application** - Create a Blazor Server demo
176+
3. **Integration Tests** - Validate end-to-end scenarios
177+
4. **Documentation** - Complete user guides and examples
178+
5. **Performance Testing** - Compare with socket.io mode
179+
180+
## Files Changed
181+
182+
### .NET
183+
- `src/ElectronNET.API/Runtime/Data/StartupMethod.cs`
184+
- `src/ElectronNET.AspNet/Hubs/ElectronHub.cs`
185+
- `src/ElectronNET.AspNet/Bridge/SignalRFacade.cs`
186+
- `src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs`
187+
- `src/ElectronNET.AspNet/API/ElectronEndpointRouteBuilderExtensions.cs`
188+
- `src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs`
189+
- `src/ElectronNET.API/Runtime/StartupManager.cs`
190+
191+
### Electron/Node.js
192+
- `src/ElectronNET.Host/package.json`
193+
- `src/ElectronNET.Host/main.js`
194+
- `src/ElectronNET.Host/api/signalr-bridge.js` (new file)
195+
196+
## Commits
197+
198+
```
199+
7f2ea48 - Add PackagedDotnetFirstSignalR and UnpackedDotnetFirstSignalR startup methods
200+
8ee81f6 - Add ElectronHub and SignalR infrastructure for new startup modes
201+
40aed60 - Add RuntimeControllerAspNetDotnetFirstSignalR for SignalR-based startup
202+
c1740b5 - Add SignalR client support to Electron Host for new startup modes
203+
cb7d721 - Add SignalRFacade for SignalR-based API communication
204+
268b9c9 - Update RuntimeControllerAspNetDotnetFirstSignalR to use SignalRFacade
205+
04ec522 - Fix compilation errors - Phase 4 complete (basic structure)
206+
054f5b1 - Complete Phase 5: Add SignalR startup detection and port 0 configuration
207+
```
208+
209+
## Benefits Over Socket.io Mode
210+
211+
- **Better Integration** - Native SignalR is part of ASP.NET Core stack
212+
- **Type Safety** - SignalR has better TypeScript support
213+
- **Performance** - SignalR is optimized for ASP.NET Core
214+
- **Reliability** - Built-in reconnection and error handling
215+
- **Scalability** - Can leverage SignalR's scale-out features
216+
- **Consistency** - Blazor Server already uses SignalR
217+
218+
## Contributing
219+
220+
To contribute to Phase 6 (full API integration):
221+
222+
1. Focus on adapting existing Electron API classes to work with SignalRFacade
223+
2. Implement request-response pattern in ElectronHub
224+
3. Add integration tests
225+
4. Create sample applications
226+
5. Update documentation
227+
228+
## License
229+
230+
MIT - Same as Electron.NET
231+
232+
---
233+
234+
**Created**: January 30, 2026
235+
**Status**: Infrastructure Complete, API Integration Pending
236+
**Contact**: See Electron.NET maintainers

src/ElectronNET.API/Bridge/BridgeConnector.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// ReSharper disable once CheckNamespace
33
namespace ElectronNET.API
44
{
5+
using ElectronNET.API.Bridge;
6+
57
internal static class BridgeConnector
68
{
7-
public static SocketIoFacade Socket
9+
public static IFacade Socket
810
{
911
get
1012
{
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace ElectronNET.API.Bridge
2+
{
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
/// <summary>
7+
/// Common interface for communication facades (SocketIO and SignalR).
8+
/// Provides methods for bidirectional communication between .NET and Electron.
9+
/// </summary>
10+
internal interface IFacade
11+
{
12+
/// <summary>
13+
/// Raised when the bridge connection is established.
14+
/// </summary>
15+
event EventHandler BridgeConnected;
16+
17+
/// <summary>
18+
/// Raised when the bridge connection is lost.
19+
/// </summary>
20+
event EventHandler BridgeDisconnected;
21+
22+
/// <summary>
23+
/// Establishes the connection to Electron.
24+
/// </summary>
25+
void Connect();
26+
27+
/// <summary>
28+
/// Registers a persistent event handler.
29+
/// </summary>
30+
void On(string eventName, Action action);
31+
32+
/// <summary>
33+
/// Registers a persistent event handler with a typed parameter.
34+
/// </summary>
35+
void On<T>(string eventName, Action<T> action);
36+
37+
/// <summary>
38+
/// Registers a one-time event handler.
39+
/// </summary>
40+
void Once(string eventName, Action action);
41+
42+
/// <summary>
43+
/// Registers a one-time event handler with a typed parameter.
44+
/// </summary>
45+
void Once<T>(string eventName, Action<T> action);
46+
47+
/// <summary>
48+
/// Removes an event handler.
49+
/// </summary>
50+
void Off(string eventName);
51+
52+
/// <summary>
53+
/// Sends a message to Electron.
54+
/// </summary>
55+
Task Emit(string eventName, params object[] args);
56+
57+
/// <summary>
58+
/// Disposes the connection.
59+
/// </summary>
60+
void DisposeSocket();
61+
}
62+
}

src/ElectronNET.API/Bridge/SocketIOFacade.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ namespace ElectronNET.API;
44

55
using System;
66
using System.Threading.Tasks;
7+
using ElectronNET.API.Bridge;
78
using ElectronNET.API.Serialization;
89
using SocketIO.Serializer.SystemTextJson;
910
using SocketIO = SocketIOClient.SocketIO;
1011

11-
internal class SocketIoFacade
12+
internal class SocketIoFacade : IFacade
1213
{
1314
private readonly SocketIO _socket;
1415
private readonly object _lockObj = new object();

src/ElectronNET.API/Common/Extensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static bool IsUnpackaged(this StartupMethod method)
1414
{
1515
case StartupMethod.UnpackedElectronFirst:
1616
case StartupMethod.UnpackedDotnetFirst:
17+
case StartupMethod.UnpackedDotnetFirstSignalR:
1718
return true;
1819
default:
1920
return false;

src/ElectronNET.API/Common/ProcessRunner.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
536536

537537
if (e.Data != null)
538538
{
539-
Console.WriteLine("|| " + e.Data);
539+
System.Diagnostics.Debug.WriteLine(e.Data);
540540
}
541541
else
542542
{
@@ -570,7 +570,7 @@ private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
570570

571571
if (e.Data != null)
572572
{
573-
Console.WriteLine("|| " + e.Data);
573+
System.Diagnostics.Debug.WriteLine(e.Data);
574574
}
575575
else
576576
{

src/ElectronNET.API/ElectronNetRuntime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace ElectronNET
22
{
33
using ElectronNET.API;
4+
using ElectronNET.API.Bridge;
45
using ElectronNET.Runtime;
56
using ElectronNET.Runtime.Controllers;
67
using ElectronNET.Runtime.Data;
@@ -49,7 +50,7 @@ static ElectronNetRuntime()
4950

5051
internal static Func<Task> OnAppReadyCallback { get; set; }
5152

52-
internal static SocketIoFacade GetSocket()
53+
internal static IFacade GetSocket()
5354
{
5455
return RuntimeControllerCore?.Socket;
5556
}

src/ElectronNET.API/Runtime/Controllers/RuntimeControllerBase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace ElectronNET.Runtime.Controllers
22
{
33
using ElectronNET.API;
4+
using ElectronNET.API.Bridge;
45
using ElectronNET.Runtime.Services;
56
using ElectronNET.Runtime.Services.ElectronProcess;
67
using ElectronNET.Runtime.Services.SocketBridge;
@@ -12,7 +13,7 @@ protected RuntimeControllerBase()
1213
{
1314
}
1415

15-
internal abstract SocketIoFacade Socket { get; }
16+
internal abstract IFacade Socket { get; }
1617

1718
internal abstract ElectronProcessBase ElectronProcess { get; }
1819

0 commit comments

Comments
 (0)