Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
7f2ea48
Add PackagedDotnetFirstSignalR and UnpackedDotnetFirstSignalR startup…
epsitec Jan 30, 2026
8ee81f6
Add ElectronHub and SignalR infrastructure for new startup modes
epsitec Jan 30, 2026
40aed60
Add RuntimeControllerAspNetDotnetFirstSignalR for SignalR-based startup
epsitec Jan 30, 2026
c1740b5
Add SignalR client support to Electron Host for new startup modes
epsitec Jan 30, 2026
cb7d721
Add SignalRFacade for SignalR-based API communication
epsitec Jan 30, 2026
268b9c9
Update RuntimeControllerAspNetDotnetFirstSignalR to use SignalRFacade
epsitec Jan 30, 2026
04ec522
Fix compilation errors - Phase 4 complete (basic structure)
epsitec Jan 30, 2026
054f5b1
Complete Phase 5: Add SignalR startup detection and port 0 configuration
epsitec Jan 30, 2026
de0c02c
Add comprehensive documentation for SignalR-based startup mode
epsitec Jan 30, 2026
5d04ab6
Add ElectronNET.Samples.BlazorSignalR - complete sample app for Signa…
epsitec Jan 30, 2026
4c17027
Add BlazorSignalR sample to solution file
epsitec Jan 30, 2026
0b92336
Fix dynamic port binding: use 127.0.0.1 instead of localhost for port 0
epsitec Jan 30, 2026
f55abb3
Fix Electron launch: subscribe to ASP.NET Ready event in SignalR cont…
epsitec Jan 30, 2026
e29a3bc
Fix IsUnpackaged extension to include UnpackedDotnetFirstSignalR
epsitec Jan 30, 2026
e9efb26
Fix main.js to check SignalR flags first before legacy mode flags
epsitec Jan 30, 2026
9135aff
Fix electronurl parameter case sensitivity for Electron command line
epsitec Jan 30, 2026
5b3d5e0
Add @microsoft/signalr to package.template.json for npm install durin…
epsitec Jan 30, 2026
c4a8de6
Fix psl dist folder issue by copying from source after npm install
epsitec Jan 30, 2026
be609a5
Refactor: Introduce IFacade interface for SocketIO and SignalR facades
epsitec Jan 30, 2026
da8216b
Implement bidirectional event routing for SignalR mode
epsitec Jan 30, 2026
4b971af
Fix: Execute app ready callback in SignalR mode
epsitec Jan 30, 2026
547e9f1
Fix: Add safe console wrapper to prevent EPIPE errors
epsitec Jan 30, 2026
8c6020e
WIP: Debugging SignalR connection timeout
epsitec Jan 30, 2026
108ef19
CRITICAL FIX: Prevent Electron from quitting in SignalR mode
epsitec Jan 30, 2026
6b9187c
Fix SignalR bridge communication issues
epsitec Jan 30, 2026
23f7924
Fix SignalR event args spreading for Electron handlers
epsitec Jan 30, 2026
06a3328
Fix window shutdown and URL port for SignalR mode
epsitec Jan 30, 2026
6e369aa
Fix static files and CSS asset reference for SignalR sample
epsitec Jan 30, 2026
1fc8816
Clean up debug logging from SignalR implementation
epsitec Jan 30, 2026
217fe83
Add documentation comments to SignalR implementation
epsitec Jan 30, 2026
12f011b
Add comprehensive SignalR implementation documentation
epsitec Jan 30, 2026
17ef685
Fix ASP0014 warning: Use top-level route registration for ElectronHub
epsitec Jan 30, 2026
7515128
Remove unnecessary UseWebSockets() call
epsitec Jan 30, 2026
6847520
Remove HSTS and HTTPS redirection for Electron apps
epsitec Jan 30, 2026
f598fbf
Phase 1.1: Generate authentication token in RuntimeController
epsitec Jan 30, 2026
dee640c
Phase 1.2: Extract token in Electron and append to URL
epsitec Jan 30, 2026
5b9e2b8
Phase 2.1: Register authentication services and middleware in Program.cs
epsitec Jan 30, 2026
6f49a66
Phase 4: Inject authentication service into RuntimeController and set…
epsitec Jan 30, 2026
893de15
Phase 3: Pass authentication token to SignalR connection URL
epsitec Jan 30, 2026
8cc3fe4
Phase 5.1: Fix quit handler to support both Socket.IO and SignalR modes
epsitec Jan 30, 2026
c12a706
Phase 5.2: Add comprehensive logging and error handling for authentic…
epsitec Jan 30, 2026
1c0b937
Phase 6: Add comprehensive authentication documentation
epsitec Jan 30, 2026
5d22456
Phase 1: Implement parallel module loading for faster startup
epsitec Jan 30, 2026
0a23659
Phase 1b: Optimize startup detection for faster .NET initialization
epsitec Jan 30, 2026
5a77284
Fix EPIPE error when .NET process terminates before Electron
epsitec Jan 31, 2026
96c454a
Add process-level EPIPE error handlers
epsitec Jan 31, 2026
39c7e61
Auto-quit Electron when pipe to .NET process breaks
epsitec Jan 31, 2026
a88e10b
Measure total startup time
epsitec Jan 31, 2026
0ee2bbe
Refactor debugger detection to use shared DebuggerHelper
epsitec Jan 31, 2026
38ee6fa
Only enable SignalR detailed errors in development
epsitec Jan 31, 2026
4a62103
Phase 1: Add environment-aware logging infrastructure
epsitec Jan 31, 2026
29b1f08
Phase 2: Clean up debugging traces and use environment-aware logging
epsitec Jan 31, 2026
805d942
Phase 3: Configure SignalR logging to reduce verbosity
epsitec Jan 31, 2026
03da5cd
Phase 4: Replace Console output with Debug.WriteLine in diagnostic code
epsitec Jan 31, 2026
52744a1
Final cleanup: Suppress remaining debug output
epsitec Jan 31, 2026
6146736
Add explicit BrowserRefresh logging filter
epsitec Jan 31, 2026
57753eb
Replace Console.WriteLine with ILogger for startup time
epsitec Jan 31, 2026
bb4337a
Cosmetic
epsitec Jan 31, 2026
bfd51e6
Fix dead-lock on shutdown
epsitec Jan 31, 2026
0fa0abd
Merge branch 'develop' of https://github.com/ElectronNET/Electron.NET…
FlorianRappl Feb 2, 2026
bbd1065
Merge branch 'epsitec-improvements' into feature/secure-connection
FlorianRappl Feb 2, 2026
931aec8
Updated lodash
FlorianRappl Feb 2, 2026
cf1cb18
Merge branch 'develop' of https://github.com/ElectronNET/Electron.NET…
FlorianRappl Feb 4, 2026
cf735fa
Original formatting
FlorianRappl Feb 4, 2026
32bcfa9
Restore logging
FlorianRappl Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
583 changes: 583 additions & 0 deletions docs/SignalR-Authentication-Guide.md

Large diffs are not rendered by default.

450 changes: 450 additions & 0 deletions docs/SignalR-Implementation-Summary.md

Large diffs are not rendered by default.

236 changes: 236 additions & 0 deletions docs/SignalR-Startup-Mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# SignalR-Based Startup Mode for Electron.NET

## Overview

This feature adds a new startup mode for Electron.NET where:
- **.NET/ASP.NET Core starts first** and binds to port 0 (dynamic port)
- **Kestrel picks an available port** automatically
- **Electron process is launched** with the actual URL
- **SignalR is used for communication** instead of socket.io
- **Blazor Server apps** can coexist with Electron control

## Status

✅ **Phases 1-5 Complete** - Infrastructure ready, basic functionality implemented
⏸️ **Phase 6 Pending** - Full API integration, testing, and documentation

## How It Works

### Startup Sequence

1. ASP.NET Core application starts
2. Kestrel binds to `http://localhost:0` (random available port)
3. `RuntimeControllerAspNetDotnetFirstSignalR` captures the actual port via `IServerAddressesFeature`
4. Electron process is launched with `--electronUrl=http://localhost:XXXXX`
5. Electron's main.js detects SignalR mode (via `--dotnetpackedsignalr` or `--unpackeddotnetsignalr` flag)
6. Electron connects to SignalR hub at `/electron-hub`
7. Hub notifies runtime controller of successful connection
8. Application transitions to "Ready" state
9. `ElectronAppReady` callback is invoked

### Communication Flow

```
.NET/Kestrel (Port 0) ←→ SignalR Hub (/electron-hub) ←→ Electron Process
↓ ↓ ↓
Blazor Server ElectronHub class SignalR Client
(/_blazor hub) (API commands) (main.js + signalr-bridge.js)
```

## Usage

### 1. Enable SignalR Mode

Set the environment variable:
```bash
ELECTRON_USE_SIGNALR=true
```

Or in launchSettings.json:
```json
{
"environmentVariables": {
"ELECTRON_USE_SIGNALR": "true"
}
}
```

### 2. Configure ASP.NET Core

In your `Program.cs`:

```csharp
using ElectronNET.API;
using ElectronNET.API.Entities;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddElectron();

builder.UseElectron(args, async () =>
{
var window = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false });

window.OnReadyToShow += () => window.Show();
});

var app = builder.Build();

// Configure middleware
app.UseStaticFiles();
app.UseRouting();

// Map the Electron SignalR hub
app.MapElectronHub(); // ← Required for SignalR mode
app.MapRazorPages();

app.Run();
```

### 3. Run Your Application

Just press F5 in Visual Studio or run:
```bash
dotnet run
```

The application will:
- Automatically detect SignalR mode via environment variable
- Bind Kestrel to port 0
- Launch Electron with the correct URL
- Establish SignalR connection

## Components

### .NET Side

- **`ElectronHub`** - SignalR hub at `/electron-hub`
- **`SignalRFacade`** - Mimics `SocketIoFacade` interface for compatibility
- **`RuntimeControllerAspNetDotnetFirstSignalR`** - Lifecycle management
- **`StartupMethod.PackagedDotnetFirstSignalR`** - For packaged apps
- **`StartupMethod.UnpackedDotnetFirstSignalR`** - For debugging

### Electron Side

- **`signalr-bridge.js`** - SignalR client wrapper
- **`main.js`** - Detects SignalR mode and connects to hub
- **`@microsoft/signalr`** npm package

## Key Features

✅ **Dynamic Port Assignment** - No hardcoded ports, no conflicts
✅ **Blazor Server Compatible** - Separate hub endpoints (`/electron-hub` vs `/_blazor`)
✅ **Bidirectional Communication** - Both .NET→Electron and Electron→.NET
✅ **Hot Reload Support** - SignalR automatic reconnection
✅ **Multiple Instances** - Each instance gets its own port

## Current Limitations (Phase 6 Work Needed)

⚠️ **Electron API Integration** - Existing Electron APIs (WindowManager, Dialog, etc.) still use SocketIoFacade. Full integration requires:
- Refactoring APIs to work with both facades, or
- Creating an adapter pattern

⚠️ **Request-Response Pattern** - Current hub methods are one-way. Need to implement proper async request-response for API calls.

⚠️ **Event Routing** - Electron events need to be routed through SignalR back to .NET.

⚠️ **Testing** - Integration tests needed to validate end-to-end functionality.

## What's Implemented

### Phase 1: Core Infrastructure ✅
- New `StartupMethod` enum values
- `ElectronHub` SignalR hub
- Hub endpoint registration

### Phase 2: Runtime Controller ✅
- `RuntimeControllerAspNetDotnetFirstSignalR`
- Port 0 binding logic
- Electron launch with URL parameter
- SignalR connection tracking

### Phase 3: Electron/Node.js Side ✅
- `@microsoft/signalr` package integration
- SignalR connection module
- Startup mode detection
- URL parameter handling

### Phase 4: API Bridge ✅ (Basic Structure)
- `SignalRFacade` class
- Event handler system
- Hub connection integration

### Phase 5: Configuration ✅
- Environment variable detection
- Port 0 configuration
- Automatic service registration

## Next Steps (Phase 6)

To fully utilize this feature, the following work is recommended:

1. **API Integration** - Make existing Electron APIs work with SignalR
2. **Sample Application** - Create a Blazor Server demo
3. **Integration Tests** - Validate end-to-end scenarios
4. **Documentation** - Complete user guides and examples
5. **Performance Testing** - Compare with socket.io mode

## Files Changed

### .NET
- `src/ElectronNET.API/Runtime/Data/StartupMethod.cs`
- `src/ElectronNET.AspNet/Hubs/ElectronHub.cs`
- `src/ElectronNET.AspNet/Bridge/SignalRFacade.cs`
- `src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs`
- `src/ElectronNET.AspNet/API/ElectronEndpointRouteBuilderExtensions.cs`
- `src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs`
- `src/ElectronNET.API/Runtime/StartupManager.cs`

### Electron/Node.js
- `src/ElectronNET.Host/package.json`
- `src/ElectronNET.Host/main.js`
- `src/ElectronNET.Host/api/signalr-bridge.js` (new file)

## Commits

```
7f2ea48 - Add PackagedDotnetFirstSignalR and UnpackedDotnetFirstSignalR startup methods
8ee81f6 - Add ElectronHub and SignalR infrastructure for new startup modes
40aed60 - Add RuntimeControllerAspNetDotnetFirstSignalR for SignalR-based startup
c1740b5 - Add SignalR client support to Electron Host for new startup modes
cb7d721 - Add SignalRFacade for SignalR-based API communication
268b9c9 - Update RuntimeControllerAspNetDotnetFirstSignalR to use SignalRFacade
04ec522 - Fix compilation errors - Phase 4 complete (basic structure)
054f5b1 - Complete Phase 5: Add SignalR startup detection and port 0 configuration
```

## Benefits Over Socket.io Mode

- **Better Integration** - Native SignalR is part of ASP.NET Core stack
- **Type Safety** - SignalR has better TypeScript support
- **Performance** - SignalR is optimized for ASP.NET Core
- **Reliability** - Built-in reconnection and error handling
- **Scalability** - Can leverage SignalR's scale-out features
- **Consistency** - Blazor Server already uses SignalR

## Contributing

To contribute to Phase 6 (full API integration):

1. Focus on adapting existing Electron API classes to work with SignalRFacade
2. Implement request-response pattern in ElectronHub
3. Add integration tests
4. Create sample applications
5. Update documentation

## License

MIT - Same as Electron.NET

---

**Created**: January 30, 2026
**Status**: Infrastructure Complete, API Integration Pending
**Contact**: See Electron.NET maintainers
4 changes: 3 additions & 1 deletion src/ElectronNET.API/Bridge/BridgeConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// ReSharper disable once CheckNamespace
namespace ElectronNET.API
{
using ElectronNET.API.Bridge;

internal static class BridgeConnector
{
public static SocketIoFacade Socket
public static IFacade Socket
{
get
{
Expand Down
62 changes: 62 additions & 0 deletions src/ElectronNET.API/Bridge/IFacade.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
namespace ElectronNET.API.Bridge
{
using System;
using System.Threading.Tasks;

/// <summary>
/// Common interface for communication facades (SocketIO and SignalR).
/// Provides methods for bidirectional communication between .NET and Electron.
/// </summary>
internal interface IFacade
{
/// <summary>
/// Raised when the bridge connection is established.
/// </summary>
event EventHandler BridgeConnected;

/// <summary>
/// Raised when the bridge connection is lost.
/// </summary>
event EventHandler BridgeDisconnected;

/// <summary>
/// Establishes the connection to Electron.
/// </summary>
void Connect();

/// <summary>
/// Registers a persistent event handler.
/// </summary>
void On(string eventName, Action action);

/// <summary>
/// Registers a persistent event handler with a typed parameter.
/// </summary>
void On<T>(string eventName, Action<T> action);

/// <summary>
/// Registers a one-time event handler.
/// </summary>
void Once(string eventName, Action action);

/// <summary>
/// Registers a one-time event handler with a typed parameter.
/// </summary>
void Once<T>(string eventName, Action<T> action);

/// <summary>
/// Removes an event handler.
/// </summary>
void Off(string eventName);

/// <summary>
/// Sends a message to Electron.
/// </summary>
Task Emit(string eventName, params object[] args);

/// <summary>
/// Disposes the connection.
/// </summary>
void DisposeSocket();
}
}
3 changes: 2 additions & 1 deletion src/ElectronNET.API/Bridge/SocketIOFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ namespace ElectronNET.API;

using System;
using System.Threading.Tasks;
using ElectronNET.API.Bridge;
using ElectronNET.API.Serialization;
using SocketIO.Serializer.SystemTextJson;
using SocketIO = SocketIOClient.SocketIO;

internal class SocketIoFacade
internal class SocketIoFacade : IFacade
{
private readonly SocketIO _socket;
private readonly object _lockObj = new object();
Expand Down
1 change: 1 addition & 0 deletions src/ElectronNET.API/Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public static bool IsUnpackaged(this StartupMethod method)
{
case StartupMethod.UnpackedElectronFirst:
case StartupMethod.UnpackedDotnetFirst:
case StartupMethod.UnpackedDotnetFirstSignalR:
return true;
default:
return false;
Expand Down
3 changes: 2 additions & 1 deletion src/ElectronNET.API/ElectronNetRuntime.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ElectronNET
{
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.Runtime;
using ElectronNET.Runtime.Controllers;
using ElectronNET.Runtime.Data;
Expand Down Expand Up @@ -49,7 +50,7 @@ static ElectronNetRuntime()

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

internal static SocketIoFacade GetSocket()
internal static IFacade GetSocket()
{
return RuntimeControllerCore?.Socket;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ElectronNET.Runtime.Controllers
{
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.Runtime.Services;
using ElectronNET.Runtime.Services.ElectronProcess;
using ElectronNET.Runtime.Services.SocketBridge;
Expand All @@ -12,7 +13,7 @@ protected RuntimeControllerBase()
{
}

internal abstract SocketIoFacade Socket { get; }
internal abstract IFacade Socket { get; }

internal abstract ElectronProcessBase ElectronProcess { get; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ElectronNET.Runtime.Controllers
{
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Helpers;
Expand All @@ -19,7 +20,7 @@ public RuntimeControllerDotNetFirst()
{
}

internal override SocketIoFacade Socket
internal override IFacade Socket
{
get
{
Expand Down
Loading
Loading