mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-09-23 07:25:16 +00:00
Renamed facades
This commit is contained in:
@@ -41,7 +41,7 @@ Blazor Server applications where:
|
||||
- Implemented Socket.IO-compatible interface for API compatibility
|
||||
|
||||
### Phase 4: API Bridge Adaptation ✅
|
||||
- Created `SignalRFacade` implementing `IFacade` interface
|
||||
- Created `SignalRConnection` implementing `ISocketConnection` interface
|
||||
- Ensured existing Electron API classes work with SignalR
|
||||
- Implemented type conversion helper for SignalR's JSON deserialization
|
||||
- Event routing from both directions (.NET ↔ Electron)
|
||||
@@ -60,8 +60,8 @@ Blazor Server applications where:
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. SignalRFacade (`src/ElectronNET.AspNet/Bridge/SignalRFacade.cs`)
|
||||
- Implements `IFacade` interface to match Socket.IO facade API
|
||||
### 1. SignalRConnection (`src/ElectronNET.AspNet/Bridge/SignalRConnection.cs`)
|
||||
- Implements `ISocketConnection` interface to match Socket.IO facade API
|
||||
- Handles bidirectional event routing using `IHubContext<ElectronHub>`
|
||||
- Includes `ConvertToType<T>` helper for handling SignalR's JSON deserialization quirks
|
||||
- Critical fix: Handles `JsonElement` and numeric type conversions (long → int)
|
||||
@@ -155,8 +155,8 @@ SignalR mode uses .NET-first startup (vs. Electron-first in Socket.IO mode) beca
|
||||
4. **Better for Blazor Server** - Blazor is already running when Electron starts
|
||||
5. **Single process debugging** - Developer debugs .NET process which owns Electron
|
||||
|
||||
### Why IFacade Interface?
|
||||
Introducing `IFacade` allows `BridgeConnector.Socket` to return either `SocketIOFacade` or `SignalRFacade` based on startup mode, ensuring existing API code works with both transport mechanisms without modification.
|
||||
### Why ISocketConnection Interface?
|
||||
Introducing `ISocketConnection` allows `BridgeConnector.Socket` to return either `SocketIOConnection` or `SignalRConnection` based on startup mode, ensuring existing API code works with both transport mechanisms without modification.
|
||||
|
||||
### Why Keep-Alive Window?
|
||||
Electron quits immediately on macOS if no windows exist. The keep-alive window ensures Electron stays running during the connection and API initialization phase. It's automatically destroyed when the first real window is created.
|
||||
@@ -208,7 +208,7 @@ Blazor Server already uses SignalR for component communication (`/_blazor` hub).
|
||||
### 3. Type Conversion Failures
|
||||
**Problem**: SignalR deserializes JSON numbers as `JsonElement` or `long`, causing `Once<int>` handlers to fail silently.
|
||||
|
||||
**Solution**: `SignalRFacade.ConvertToType<T>` handles JsonElement deserialization and numeric conversions.
|
||||
**Solution**: `SignalRConnection.ConvertToType<T>` handles JsonElement deserialization and numeric conversions.
|
||||
|
||||
### 4. Window Shutdown Not Triggering Exit
|
||||
**Problem**: Keep-alive window prevented `window-all-closed` event from firing.
|
||||
@@ -391,7 +391,7 @@ Existing applications do not need to change. SignalR mode is opt-in via command-
|
||||
## File Changes Summary
|
||||
|
||||
**New Files**:
|
||||
- `src/ElectronNET.AspNet/Bridge/SignalRFacade.cs` (225 lines)
|
||||
- `src/ElectronNET.AspNet/Bridge/SignalRConnection.cs` (225 lines)
|
||||
- `src/ElectronNET.AspNet/Hubs/ElectronHub.cs` (108 lines)
|
||||
- `src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs` (163 lines)
|
||||
- `src/ElectronNET.AspNet/Services/IElectronAuthenticationService.cs` (20 lines)
|
||||
|
||||
@@ -107,7 +107,7 @@ The application will:
|
||||
### .NET Side
|
||||
|
||||
- **`ElectronHub`** - SignalR hub at `/electron-hub`
|
||||
- **`SignalRFacade`** - Mimics `SocketIoFacade` interface for compatibility
|
||||
- **`SignalRConnection`** - Mimics `SocketIOConnection` interface for compatibility
|
||||
- **`RuntimeControllerAspNetDotnetFirstSignalR`** - Lifecycle management
|
||||
- **`StartupMethod.PackagedDotnetFirstSignalR`** - For packaged apps
|
||||
- **`StartupMethod.UnpackedDotnetFirstSignalR`** - For debugging
|
||||
@@ -128,7 +128,7 @@ The application will:
|
||||
|
||||
## Current Limitations (Phase 6 Work Needed)
|
||||
|
||||
⚠️ **Electron API Integration** - Existing Electron APIs (WindowManager, Dialog, etc.) still use SocketIoFacade. Full integration requires:
|
||||
⚠️ **Electron API Integration** - Existing Electron APIs (WindowManager, Dialog, etc.) still use SocketIOConnection. Full integration requires:
|
||||
- Refactoring APIs to work with both facades, or
|
||||
- Creating an adapter pattern
|
||||
|
||||
@@ -158,7 +158,7 @@ The application will:
|
||||
- URL parameter handling
|
||||
|
||||
### Phase 4: API Bridge ✅ (Basic Structure)
|
||||
- `SignalRFacade` class
|
||||
- `SignalRConnection` class
|
||||
- Event handler system
|
||||
- Hub connection integration
|
||||
|
||||
@@ -182,7 +182,7 @@ To fully utilize this feature, the following work is recommended:
|
||||
### .NET
|
||||
- `src/ElectronNET.API/Runtime/Data/StartupMethod.cs`
|
||||
- `src/ElectronNET.AspNet/Hubs/ElectronHub.cs`
|
||||
- `src/ElectronNET.AspNet/Bridge/SignalRFacade.cs`
|
||||
- `src/ElectronNET.AspNet/Bridge/SignalRConnection.cs`
|
||||
- `src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs`
|
||||
- `src/ElectronNET.AspNet/API/ElectronEndpointRouteBuilderExtensions.cs`
|
||||
- `src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs`
|
||||
@@ -200,8 +200,8 @@ To fully utilize this feature, the following work is recommended:
|
||||
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
|
||||
cb7d721 - Add SignalRConnection for SignalR-based API communication
|
||||
268b9c9 - Update RuntimeControllerAspNetDotnetFirstSignalR to use SignalRConnection
|
||||
04ec522 - Fix compilation errors - Phase 4 complete (basic structure)
|
||||
054f5b1 - Complete Phase 5: Add SignalR startup detection and port 0 configuration
|
||||
```
|
||||
@@ -219,7 +219,7 @@ cb7d721 - Add SignalRFacade for SignalR-based API communication
|
||||
|
||||
To contribute to Phase 6 (full API integration):
|
||||
|
||||
1. Focus on adapting existing Electron API classes to work with SignalRFacade
|
||||
1. Focus on adapting existing Electron API classes to work with SignalRConnection
|
||||
2. Implement request-response pattern in ElectronHub
|
||||
3. Add integration tests
|
||||
4. Create sample applications
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ElectronNET.API
|
||||
|
||||
internal static class BridgeConnector
|
||||
{
|
||||
public static IFacade Socket
|
||||
public static ISocketConnection Socket
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace ElectronNET.API.Bridge
|
||||
/// Common interface for communication facades (SocketIO and SignalR).
|
||||
/// Provides methods for bidirectional communication between .NET and Electron.
|
||||
/// </summary>
|
||||
internal interface IFacade
|
||||
internal interface ISocketConnection : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when the bridge connection is established.
|
||||
@@ -53,10 +53,5 @@ namespace ElectronNET.API.Bridge
|
||||
/// Sends a message to Electron.
|
||||
/// </summary>
|
||||
Task Emit(string eventName, params object[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the connection.
|
||||
/// </summary>
|
||||
void DisposeSocket();
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,13 @@ using ElectronNET.API.Serialization;
|
||||
using SocketIO.Serializer.SystemTextJson;
|
||||
using SocketIO = SocketIOClient.SocketIO;
|
||||
|
||||
internal class SocketIoFacade : IFacade, IDisposable
|
||||
internal class SocketIOConnection : ISocketConnection
|
||||
{
|
||||
private readonly SocketIO _socket;
|
||||
private readonly object _lockObj = new object();
|
||||
private bool _isDisposed;
|
||||
|
||||
public SocketIoFacade(string uri)
|
||||
public SocketIOConnection(string uri)
|
||||
{
|
||||
_socket = new SocketIO(uri);
|
||||
_socket.Serializer = new SystemTextJsonSerializer(ElectronJson.Options);
|
||||
@@ -141,7 +141,7 @@ internal class SocketIoFacade : IFacade, IDisposable
|
||||
{
|
||||
if (this._isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(SocketIoFacade));
|
||||
throw new ObjectDisposedException(nameof(SocketIOConnection));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
internal static Func<Task> OnAppReadyCallback { get; set; }
|
||||
|
||||
internal static IFacade GetSocket()
|
||||
internal static ISocketConnection GetSocket()
|
||||
{
|
||||
return RuntimeControllerCore?.Socket;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{
|
||||
}
|
||||
|
||||
internal abstract IFacade Socket { get; }
|
||||
internal abstract ISocketConnection Socket { get; }
|
||||
|
||||
internal abstract ElectronProcessBase ElectronProcess { get; }
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
}
|
||||
|
||||
internal override IFacade Socket
|
||||
internal override ISocketConnection Socket
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{
|
||||
}
|
||||
|
||||
internal override SocketIoFacade Socket
|
||||
internal override SocketIOConnection Socket
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{
|
||||
private readonly int socketPort;
|
||||
private readonly string socketUrl;
|
||||
private SocketIoFacade socket;
|
||||
private SocketIOConnection socket;
|
||||
|
||||
public SocketBridgeService(int socketPort)
|
||||
{
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
public int SocketPort => this.socketPort;
|
||||
|
||||
internal SocketIoFacade Socket => this.socket;
|
||||
internal SocketIOConnection Socket => this.socket;
|
||||
|
||||
protected override Task StartCore()
|
||||
{
|
||||
this.socket = new SocketIoFacade(this.socketUrl);
|
||||
this.socket = new SocketIOConnection(this.socketUrl);
|
||||
this.socket.BridgeConnected += this.Socket_BridgeConnected;
|
||||
this.socket.BridgeDisconnected += this.Socket_BridgeDisconnected;
|
||||
Task.Run(this.Connect);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace ElectronNET.API
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using ElectronNET.AspNet;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ElectronNET.API
|
||||
using ElectronNET.AspNet.Hubs;
|
||||
|
||||
/// <summary>
|
||||
/// SignalR-based facade that mimics the SocketIoFacade interface
|
||||
/// SignalR-based facade that mimics the SocketIOConnection interface
|
||||
/// for compatibility with existing Electron API code.
|
||||
///
|
||||
/// Key implementation details:
|
||||
@@ -19,14 +19,14 @@ namespace ElectronNET.API
|
||||
/// - Event args are passed as arrays to match SignalR serialization behavior
|
||||
/// - Connection ID is set by ElectronHub when Electron client connects
|
||||
/// </summary>
|
||||
internal class SignalRFacade : IFacade
|
||||
internal class SignalRConnection : ISocketConnection
|
||||
{
|
||||
private readonly IHubContext<ElectronHub> _hubContext;
|
||||
private string _connectionId;
|
||||
private readonly ConcurrentDictionary<string, Action<object>> _eventHandlers;
|
||||
private readonly object _lockObj = new object();
|
||||
|
||||
public SignalRFacade(IHubContext<ElectronHub> hubContext)
|
||||
public SignalRConnection(IHubContext<ElectronHub> hubContext)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
_eventHandlers = new ConcurrentDictionary<string, Action<object>>();
|
||||
@@ -76,7 +76,7 @@ namespace ElectronNET.API
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[SignalRFacade] Failed to convert event data to type {typeof(T).Name}");
|
||||
Console.Error.WriteLine($"[SignalR] Failed to convert event data to type {typeof(T).Name}");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace ElectronNET.API
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[SignalRFacade] Failed to convert event data to type {typeof(T).Name} for event '{eventName}'");
|
||||
Console.Error.WriteLine($"[SignalR] Failed to convert event data to type {typeof(T).Name} for event '{eventName}'");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -126,7 +126,7 @@ namespace ElectronNET.API
|
||||
{
|
||||
if (string.IsNullOrEmpty(_connectionId))
|
||||
{
|
||||
Console.Error.WriteLine($"[SignalRFacade] Cannot emit '{eventName}' - no connection ID");
|
||||
Console.Error.WriteLine($"[SignalR] Cannot emit '{eventName}' - no connection ID");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace ElectronNET.API
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[SignalRFacade] Error emitting '{eventName}': {ex.Message}");
|
||||
Console.Error.WriteLine($"[SignalR] Error emitting '{eventName}': {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,7 @@ namespace ElectronNET.API
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[SignalRFacade] JsonElement deserialization failed: {ex.Message}");
|
||||
Console.Error.WriteLine($"[SignalR] JsonElement deserialization failed: {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -218,15 +218,25 @@ namespace ElectronNET.API
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[SignalRFacade] Type conversion failed from {obj.GetType().Name} to {targetType.Name}: {ex.Message}");
|
||||
Console.Error.WriteLine($"[SignalR] Type conversion failed from {obj.GetType().Name} to {targetType.Name}: {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisposeSocket()
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// SignalR connections are managed by ASP.NET Core
|
||||
_eventHandlers.Clear();
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// SignalR connections are managed by ASP.NET Core
|
||||
_eventHandlers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,12 +65,12 @@ namespace ElectronNET.AspNet.Hubs
|
||||
/// <param name="args">The event arguments as an array</param>
|
||||
public async Task ElectronEvent(string eventName, object[] args)
|
||||
{
|
||||
// Get the SignalRFacade and trigger the event handlers
|
||||
// Get the SignalRConnection and trigger the event handlers
|
||||
var runtimeController = ElectronNetRuntime.RuntimeController as RuntimeControllerAspNetDotnetFirstSignalR;
|
||||
if (runtimeController?.SignalRSocket is SignalRFacade signalRFacade)
|
||||
if (runtimeController?.SignalRSocket is SignalRConnection socket)
|
||||
{
|
||||
// Invoke the event handlers registered via On/Once
|
||||
signalRFacade.TriggerEvent(eventName, args ?? Array.Empty<object>());
|
||||
socket.TriggerEvent(eventName, args ?? Array.Empty<object>());
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
internal override SocketBridgeService SocketBridge => this.socketBridge;
|
||||
|
||||
internal override IFacade Socket
|
||||
internal override ISocketConnection Socket
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace ElectronNET.AspNet.Runtime
|
||||
/// Key differences from Socket.IO mode:
|
||||
/// - Waits for ASP.NET server to start, then captures the dynamic port
|
||||
/// - Launches Electron with the actual URL (no port scanning needed)
|
||||
/// - Uses SignalRFacade instead of SocketIOFacade for bidirectional communication
|
||||
/// - Uses SignalRConnection instead of SocketIOConnection for bidirectional communication
|
||||
/// - Waits for 'electron-host-ready' signal to ensure API modules are loaded before calling app callback
|
||||
/// </summary>
|
||||
internal class RuntimeControllerAspNetDotnetFirstSignalR : RuntimeControllerAspNetBase
|
||||
@@ -29,7 +29,7 @@ namespace ElectronNET.AspNet.Runtime
|
||||
private readonly IServer server;
|
||||
private readonly IHubContext<ElectronHub> hubContext;
|
||||
private readonly IElectronAuthenticationService authenticationService;
|
||||
private SignalRFacade signalRFacade;
|
||||
private SignalRConnection socket;
|
||||
private int? port;
|
||||
private string actualUrl;
|
||||
private bool electronLaunched;
|
||||
@@ -45,11 +45,11 @@ namespace ElectronNET.AspNet.Runtime
|
||||
this.server = server;
|
||||
this.hubContext = hubContext;
|
||||
this.authenticationService = authenticationService;
|
||||
this.signalRFacade = new SignalRFacade(hubContext);
|
||||
this.socket = new SignalRConnection(hubContext);
|
||||
this.electronLaunched = false;
|
||||
|
||||
this.signalRFacade.BridgeConnected += this.SignalRFacade_Connected;
|
||||
this.signalRFacade.BridgeDisconnected += this.SignalRFacade_Disconnected;
|
||||
this.socket.BridgeConnected += this.SignalRConnection_Connected;
|
||||
this.socket.BridgeDisconnected += this.SignalRConnection_Disconnected;
|
||||
|
||||
// Subscribe to ASP.NET ready event to launch Electron
|
||||
aspNetLifetimeAdapter.Ready += this.OnAspNetReady;
|
||||
@@ -58,20 +58,20 @@ namespace ElectronNET.AspNet.Runtime
|
||||
internal override ElectronProcessBase ElectronProcess => this.electronProcess;
|
||||
internal override SocketBridgeService SocketBridge => null;
|
||||
|
||||
internal override IFacade Socket
|
||||
internal override ISocketConnection Socket
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.State == LifetimeState.Ready)
|
||||
{
|
||||
return this.signalRFacade;
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot access SignalR facade. Runtime is not in 'Ready' state");
|
||||
}
|
||||
}
|
||||
|
||||
internal SignalRFacade SignalRSocket => this.signalRFacade;
|
||||
internal SignalRConnection SignalRSocket => this.socket;
|
||||
|
||||
protected override Task StartCore()
|
||||
{
|
||||
@@ -81,7 +81,7 @@ namespace ElectronNET.AspNet.Runtime
|
||||
protected override Task StopCore()
|
||||
{
|
||||
this.electronProcess?.Stop();
|
||||
this.signalRFacade?.DisposeSocket();
|
||||
this.socket?.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -132,11 +132,11 @@ namespace ElectronNET.AspNet.Runtime
|
||||
_ = this.electronProcess.Start();
|
||||
}
|
||||
|
||||
private async void SignalRFacade_Connected(object sender, EventArgs e)
|
||||
private async void SignalRConnection_Connected(object sender, EventArgs e)
|
||||
{
|
||||
// Register handler for 'electron-host-ready' signal from Electron.
|
||||
// This ensures API modules are fully loaded before calling the app ready callback.
|
||||
this.signalRFacade.Once("electron-host-ready", () =>
|
||||
this.socket.Once("electron-host-ready", () =>
|
||||
{
|
||||
this.OnElectronHostReady();
|
||||
});
|
||||
@@ -161,7 +161,7 @@ namespace ElectronNET.AspNet.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
private void SignalRFacade_Disconnected(object sender, EventArgs e)
|
||||
private void SignalRConnection_Disconnected(object sender, EventArgs e)
|
||||
{
|
||||
// IMPORTANT: Do NOT call HandleStopped synchronously here!
|
||||
// This event fires from within SignalR's OnDisconnectedAsync, and calling
|
||||
@@ -178,12 +178,12 @@ namespace ElectronNET.AspNet.Runtime
|
||||
|
||||
public void OnSignalRConnected(string connectionId)
|
||||
{
|
||||
this.signalRFacade.SetConnectionId(connectionId);
|
||||
this.socket.SetConnectionId(connectionId);
|
||||
}
|
||||
|
||||
public void OnSignalRDisconnected()
|
||||
{
|
||||
this.signalRFacade.OnDisconnected();
|
||||
this.socket.OnDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user