Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions src/ElectronNET.API/Bridge/SocketIOConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,29 @@
namespace ElectronNET.API;

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronNET.API.Serialization;
using SocketIO.Serializer.SystemTextJson;
using SocketIO = SocketIOClient.SocketIO;
using SocketIOOptions = SocketIOClient.SocketIOOptions;

internal class SocketIOConnection : ISocketConnection
{
private readonly SocketIO _socket;
private readonly object _lockObj = new object();
private bool _isDisposed;

public SocketIOConnection(string uri)
public SocketIOConnection(string uri, string authorization)
{
_socket = new SocketIO(uri);
var opts = string.IsNullOrEmpty(authorization) ? new SocketIOOptions() : new SocketIOOptions
{
ExtraHeaders = new Dictionary<string, string>
{
["authorization"] = authorization
},
};
_socket = new SocketIO(uri, opts);
_socket.Serializer = new SystemTextJsonSerializer(ElectronJson.Options);
// Use default System.Text.Json serializer from SocketIOClient.
// Outgoing args are normalized to camelCase via SerializeArg in Emit.
Expand Down
3 changes: 3 additions & 0 deletions src/ElectronNET.API/Common/ProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class ProcessRunner : IDisposable
private readonly StringBuilder stdOut = new StringBuilder(4 * 1024);
private readonly StringBuilder stdErr = new StringBuilder(4 * 1024);

public event EventHandler<string> LineReceived;

private volatile ManualResetEvent stdOutEvent;
private volatile ManualResetEvent stdErrEvent;
private volatile Stopwatch stopwatch;
Expand Down Expand Up @@ -571,6 +573,7 @@ private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
if (e.Data != null)
{
Console.WriteLine("|| " + e.Data);
LineReceived?.Invoke(this, e.Data);
}
else
{
Expand Down
3 changes: 3 additions & 0 deletions src/ElectronNET.API/ElectronNetRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class ElectronNetRuntime
internal const int DefaultWebPort = 8001;
internal const string ElectronPortArgumentName = "electronPort";
internal const string ElectronPidArgumentName = "electronPID";
internal const string ElectronAuthTokenArgumentName = "electronAuthToken";

/// <summary>Initializes the <see cref="ElectronNetRuntime"/> class.</summary>
static ElectronNetRuntime()
Expand All @@ -26,6 +27,8 @@ static ElectronNetRuntime()

public static string ElectronExtraArguments { get; set; }

public static string ElectronAuthToken { get; internal set; }

public static int? ElectronSocketPort { get; internal set; }

public static int? AspNetWebPort { get; internal set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ internal class RuntimeControllerDotNetFirst : RuntimeControllerBase
{
private ElectronProcessBase electronProcess;
private SocketBridgeService socketBridge;
private int? port;

public RuntimeControllerDotNetFirst()
{
Expand Down Expand Up @@ -41,19 +40,13 @@ protected override Task StartCore()
var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
var args = string.Format("{0} {1}", ElectronNetRuntime.ElectronExtraArguments, Environment.CommandLine).Trim();
this.port = ElectronNetRuntime.ElectronSocketPort;

if (!this.port.HasValue)
{
this.port = PortHelper.GetFreePort(ElectronNetRuntime.DefaultSocketPort);
ElectronNetRuntime.ElectronSocketPort = this.port;
}
var port = ElectronNetRuntime.ElectronSocketPort ?? 0;

Console.Error.WriteLine("[StartCore]: isUnPacked: {0}", isUnPacked);
Console.Error.WriteLine("[StartCore]: electronBinaryName: {0}", electronBinaryName);
Console.Error.WriteLine("[StartCore]: args: {0}", args);

this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, this.port.Value);
this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, port);
this.electronProcess.Ready += this.ElectronProcess_Ready;
this.electronProcess.Stopped += this.ElectronProcess_Stopped;

Expand All @@ -63,8 +56,10 @@ protected override Task StartCore()

private void ElectronProcess_Ready(object sender, EventArgs e)
{
var port = ElectronNetRuntime.ElectronSocketPort.Value;
var token = ElectronNetRuntime.ElectronAuthToken;
this.TransitionState(LifetimeState.Started);
this.socketBridge = new SocketBridgeService(this.port!.Value);
this.socketBridge = new SocketBridgeService(port, token);
this.socketBridge.Ready += this.SocketBridge_Ready;
this.socketBridge.Stopped += this.SocketBridge_Stopped;
this.socketBridge.Start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ internal class RuntimeControllerElectronFirst : RuntimeControllerBase
{
private ElectronProcessBase electronProcess;
private SocketBridgeService socketBridge;
private int? port;

public RuntimeControllerElectronFirst()
{
Expand All @@ -36,20 +35,16 @@ internal override ISocketConnection Socket

protected override Task StartCore()
{
this.port = ElectronNetRuntime.ElectronSocketPort;

if (!this.port.HasValue)
{
throw new Exception("No port has been specified by Electron!");
}
var port = ElectronNetRuntime.ElectronSocketPort.Value;
var token = ElectronNetRuntime.ElectronAuthToken;

if (!ElectronNetRuntime.ElectronProcessId.HasValue)
{
throw new Exception("No electronPID has been specified by Electron!");
}

this.TransitionState(LifetimeState.Starting);
this.socketBridge = new SocketBridgeService(this.port!.Value);
this.socketBridge = new SocketBridgeService(port, token);
this.socketBridge.Ready += this.SocketBridge_Ready;
this.socketBridge.Stopped += this.SocketBridge_Stopped;
this.socketBridge.Start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
{
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
Expand All @@ -15,6 +18,7 @@
[Localizable(false)]
internal class ElectronProcessActive : ElectronProcessBase
{
private readonly Regex extractor = new Regex("^Electron Socket: listening on port (\\d+) at .* using ([a-f0-9]+)$");
private readonly bool isUnpackaged;
private readonly string electronBinaryName;
private readonly string extraArguments;
Expand Down Expand Up @@ -101,7 +105,6 @@ private void CheckRuntimeIdentifier()
}

var osPart = buildInfoRid.Split('-').First();

var mismatch = false;

switch (osPart)
Expand Down Expand Up @@ -157,18 +160,57 @@ protected override Task StopCore()

private async Task StartInternal(string startCmd, string args, string directoriy)
{
try
var tcs = new TaskCompletionSource();
using var cts = new CancellationTokenSource(2 * 60_000); // cancel after 2 minutes
using var _ = cts.Token.Register(() =>
{
// Time is over - let's kill the process and move on
this.process.Cancel();
// We don't want to raise exceptions here - just pass the barrier
tcs.SetResult();
});

void Read_SocketIO_Parameters(object sender, string line)
{
// Look for "Electron Socket: listening on port %s at ..."
var match = extractor.Match(line);

if (match?.Success ?? false)
{
var port = int.Parse(match.Groups[1].Value);
var token = match.Groups[2].Value;

this.process.LineReceived -= Read_SocketIO_Parameters;
ElectronNetRuntime.ElectronAuthToken = token;
ElectronNetRuntime.ElectronSocketPort = port;
tcs.SetResult();
}
}

void Monitor_SocketIO_Failure(object sender, EventArgs e)
{
await Task.Delay(10.ms()).ConfigureAwait(false);
// We don't want to raise exceptions here - just pass the barrier
if (tcs.Task.IsCompleted)
{
this.Process_Exited(sender, e);
}
else
{
tcs.SetResult();
}
}

try
{
Console.Error.WriteLine("[StartInternal]: startCmd: {0}", startCmd);
Console.Error.WriteLine("[StartInternal]: args: {0}", args);

this.process = new ProcessRunner("ElectronRunner");
this.process.ProcessExited += this.Process_Exited;
this.process.ProcessExited += Monitor_SocketIO_Failure;
this.process.LineReceived += Read_SocketIO_Parameters;
this.process.Run(startCmd, args, directoriy);

await Task.Delay(500.ms()).ConfigureAwait(false);
await tcs.Task.ConfigureAwait(false);

Console.Error.WriteLine("[StartInternal]: after run:");

Expand All @@ -178,11 +220,11 @@ private async Task StartInternal(string startCmd, string args, string directoriy
Console.Error.WriteLine("[StartInternal]: Process is not running: " + this.process.StandardOutput);

Task.Run(() => this.TransitionState(LifetimeState.Stopped));

throw new Exception("Failed to launch the Electron process.");
}

this.TransitionState(LifetimeState.Ready);
else
{
this.TransitionState(LifetimeState.Ready);
}
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
internal class SocketBridgeService : LifetimeServiceBase
{
private readonly int socketPort;
private readonly string authorization;
private readonly string socketUrl;
private SocketIOConnection socket;

public SocketBridgeService(int socketPort)
public SocketBridgeService(int socketPort, string authorization)
{
this.socketPort = socketPort;
this.authorization = authorization;
this.socketUrl = $"http://localhost:{this.socketPort}";
}

Expand All @@ -23,7 +25,7 @@ public SocketBridgeService(int socketPort)

protected override Task StartCore()
{
this.socket = new SocketIOConnection(this.socketUrl);
this.socket = new SocketIOConnection(this.socketUrl, this.authorization);
this.socket.BridgeConnected += this.Socket_BridgeConnected;
this.socket.BridgeDisconnected += this.Socket_BridgeDisconnected;
Task.Run(this.Connect);
Expand Down
14 changes: 14 additions & 0 deletions src/ElectronNET.API/Runtime/StartupManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ private void CollectProcessData()
Console.WriteLine("Electron Process ID: " + result);
}
}

var authTokenArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronAuthTokenArgumentName, StringComparison.OrdinalIgnoreCase));

if (authTokenArg != null)
{
var parts = authTokenArg.Split('=', StringSplitOptions.TrimEntries);

if (parts.Length > 1 && !string.IsNullOrWhiteSpace(parts[1]))
{
var result = parts[1];
ElectronNetRuntime.ElectronAuthToken = result;
Console.WriteLine("Use Auth Token: " + result);
}
}
}

private void SetElectronExecutable()
Expand Down
15 changes: 10 additions & 5 deletions src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ElectronNET.API
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using ElectronNET.AspNet;
Expand All @@ -10,6 +11,7 @@
using ElectronNET.Runtime.Helpers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

/// <summary>
/// Provides extension methods for <see cref="IWebHostBuilder"/> to enable Electron.NET
Expand Down Expand Up @@ -66,23 +68,26 @@ public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[]
// work as expected, see issue #952
Environment.SetEnvironmentVariable("ELECTRON_RUN_AS_NODE", null);

var webPort = PortHelper.GetFreePort(ElectronNetRuntime.AspNetWebPort ?? ElectronNetRuntime.DefaultWebPort);
ElectronNetRuntime.AspNetWebPort = webPort;
var webPort = ElectronNetRuntime.AspNetWebPort ?? 0;

// check for the content folder if its exists in base director otherwise no need to include
// It was used before because we are publishing the project which copies everything to bin folder and contentroot wwwroot was folder there.
// now we have implemented the live reload if app is run using /watch then we need to use the default project path.

// For port 0 (dynamic port assignment), Kestrel requires binding to specific IP (127.0.0.1) not localhost
var host = webPort == 0 ? "127.0.0.1" : "localhost";

if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
{
builder = builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
.UseUrls("http://localhost:" + webPort);
.UseUrls($"http://{host}:{webPort}");
}
else
{
builder = builder.UseUrls("http://localhost:" + webPort);
builder = builder.UseUrls($"http://{host}:{webPort}");
}

builder = builder.ConfigureServices(services =>
builder = builder.ConfigureServices((context, services) =>
{
services.AddTransient<IStartupFilter, ServerReadyStartupFilter>();
services.AddSingleton<AspNetLifetimeAdapter>();
Expand Down
Loading
Loading