mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-09-22 06:54:44 +00:00
Add experimental mode to start electron from C# (for development purposes)
This commit is contained in:
@@ -476,6 +476,13 @@ namespace ElectronNET.API
|
||||
Log("ElectronNET socket {1} failed to connect {0}", ex, socket.Id);
|
||||
};
|
||||
|
||||
|
||||
socket.OnReconnectFailed += (_, ex) =>
|
||||
{
|
||||
_connectedSocketEvent.Reset();
|
||||
Log("ElectronNET socket {1} failed to reconnect {0}", ex, socket.Id);
|
||||
};
|
||||
|
||||
socket.OnReconnected += (_, __) =>
|
||||
{
|
||||
_connectedSocketEvent.Set();
|
||||
@@ -490,8 +497,8 @@ namespace ElectronNET.API
|
||||
|
||||
socket.OnError += (_, msg) =>
|
||||
{
|
||||
//_connectedSocketEvent.Reset();
|
||||
Log("ElectronNET socket {1} error: {0}...", msg, socket.Id);
|
||||
//_connectedSocketEvent.Reset();
|
||||
Log("ElectronNET socket {1} error: {0}...", msg, socket.Id);
|
||||
};
|
||||
|
||||
_socket = socket;
|
||||
@@ -530,7 +537,7 @@ namespace ElectronNET.API
|
||||
}
|
||||
|
||||
internal static ILogger<App> Logger { private get; set; }
|
||||
internal static string AuthKey { private get; set; }
|
||||
internal static string AuthKey { get; set; } = null;
|
||||
|
||||
private class CamelCaseNewtonsoftJsonSerializer : NewtonsoftJsonSerializer
|
||||
{
|
||||
|
||||
142
ElectronNET.API/Electron.Experimental.cs
Normal file
142
ElectronNET.API/Electron.Experimental.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ElectronNET.API
|
||||
{
|
||||
public static partial class Electron
|
||||
{
|
||||
/// <summary>
|
||||
/// Experimental code, use with care
|
||||
/// </summary>
|
||||
public static class Experimental
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts electron from C#, use during development to avoid having to fully publish / build your app on every compile cycle
|
||||
/// You will need to run the CLI at least once (and once per update) to bootstrap all required files
|
||||
/// </summary>
|
||||
/// <param name="webPort"></param>
|
||||
/// <param name="projectPath"></param>
|
||||
/// <param name="extraElectronArguments"></param>
|
||||
/// <param name="clearCache"></param>
|
||||
/// <exception cref="DirectoryNotFoundException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task StartElectronForDevelopment(int webPort, string projectPath = null, string[] extraElectronArguments = null, bool clearCache = false)
|
||||
{
|
||||
string aspCoreProjectPath;
|
||||
|
||||
if (!string.IsNullOrEmpty(projectPath))
|
||||
{
|
||||
if (Directory.Exists(projectPath))
|
||||
{
|
||||
aspCoreProjectPath = projectPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DirectoryNotFoundException(projectPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aspCoreProjectPath = Directory.GetCurrentDirectory();
|
||||
}
|
||||
|
||||
string tempPath = Path.Combine(aspCoreProjectPath, "obj", "Host");
|
||||
|
||||
if (!Directory.Exists(tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(tempPath);
|
||||
}
|
||||
|
||||
var mainFileJs = Path.Combine(tempPath, "main.js");
|
||||
if (!File.Exists(mainFileJs))
|
||||
{
|
||||
throw new Exception("You need to run once the electronize-h5 start command to bootstrap the necessary files");
|
||||
}
|
||||
|
||||
var nodeModulesDirPath = Path.Combine(tempPath, "node_modules");
|
||||
|
||||
bool runNpmInstall = false;
|
||||
|
||||
if (!Directory.Exists(nodeModulesDirPath))
|
||||
{
|
||||
runNpmInstall = true;
|
||||
}
|
||||
|
||||
var packagesJson = Path.Combine(tempPath, "package.json");
|
||||
|
||||
var packagesPrevious = Path.Combine(tempPath, "package.json.previous");
|
||||
|
||||
if (!runNpmInstall)
|
||||
{
|
||||
|
||||
if (File.Exists(packagesPrevious))
|
||||
{
|
||||
if (File.ReadAllText(packagesPrevious) != File.ReadAllText(packagesJson))
|
||||
{
|
||||
runNpmInstall = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
runNpmInstall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (runNpmInstall)
|
||||
{
|
||||
throw new Exception("You need to run once the electronize-h5 start command to bootstrap the necessary files");
|
||||
}
|
||||
|
||||
string arguments = "";
|
||||
|
||||
if (extraElectronArguments is object)
|
||||
{
|
||||
arguments = string.Join(' ', extraElectronArguments);
|
||||
}
|
||||
|
||||
if (clearCache)
|
||||
{
|
||||
arguments += " --clear-cache=true";
|
||||
}
|
||||
|
||||
BridgeConnector.AuthKey = Guid.NewGuid().ToString().Replace("-", "");
|
||||
|
||||
var socketPort = FreeTcpPort();
|
||||
|
||||
arguments += $" --development=true --devauth={BridgeConnector.AuthKey} --devport={socketPort}";
|
||||
|
||||
string path = Path.Combine(tempPath, "node_modules", ".bin");
|
||||
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
if (isWindows)
|
||||
{
|
||||
ProcessHelper.Execute(@"electron.cmd ""..\..\main.js"" " + arguments, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessHelper.Execute(@"./electron ""../../main.js"" " + arguments, path);
|
||||
}
|
||||
|
||||
BridgeSettings.InitializePorts(socketPort, webPort);
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a free local TCP port
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int FreeTcpPort()
|
||||
{
|
||||
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
|
||||
l.Start();
|
||||
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
||||
l.Stop();
|
||||
return port;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.Versioning;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ElectronNET.API
|
||||
{
|
||||
/// <summary>
|
||||
/// The Electron.NET API
|
||||
/// </summary>
|
||||
public static class Electron
|
||||
public static partial class Electron
|
||||
{
|
||||
private static ILoggerFactory loggerFactory;
|
||||
|
||||
@@ -17,7 +18,13 @@ namespace ElectronNET.API
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static void ReadAuth()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(BridgeConnector.AuthKey))
|
||||
{
|
||||
throw new Exception($"Don't call ReadAuth twice or from with {nameof(Experimental)}.{nameof(Experimental.StartElectronForDevelopment)}");
|
||||
}
|
||||
|
||||
var line = Console.ReadLine();
|
||||
|
||||
if(line.StartsWith("Auth="))
|
||||
{
|
||||
BridgeConnector.AuthKey = line.Substring("Auth=".Length);
|
||||
|
||||
37
ElectronNET.API/ProcessHelper.cs
Normal file
37
ElectronNET.API/ProcessHelper.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ElectronNET.API
|
||||
{
|
||||
internal class ProcessHelper
|
||||
{
|
||||
public static void Execute(string command, string workingDirectoryPath)
|
||||
{
|
||||
using (Process cmd = new Process())
|
||||
{
|
||||
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
if (isWindows)
|
||||
{
|
||||
cmd.StartInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
|
||||
}
|
||||
else
|
||||
{
|
||||
// works for OSX and Linux (at least on Ubuntu)
|
||||
var escapedArgs = command.Replace("\"", "\\\"");
|
||||
cmd.StartInfo = new ProcessStartInfo("bash", $"-c \"{escapedArgs}\"");
|
||||
}
|
||||
|
||||
cmd.StartInfo.RedirectStandardInput = false;
|
||||
cmd.StartInfo.RedirectStandardOutput = false;
|
||||
cmd.StartInfo.RedirectStandardError = false;
|
||||
cmd.StartInfo.CreateNoWindow = true;
|
||||
cmd.StartInfo.UseShellExecute = false;
|
||||
cmd.StartInfo.WorkingDirectory = workingDirectoryPath;
|
||||
cmd.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ const crypto = require('crypto');
|
||||
|
||||
fixPath(); //For macOS and Linux packaged-apps, the path variable might be missing
|
||||
|
||||
const auth = crypto.randomBytes(32).toString('hex');
|
||||
let auth = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
let io, server, browserWindows, ipc, apiProcess, loadURL;
|
||||
let appApi, menu, dialogApi, notification, tray, webContents;
|
||||
@@ -25,6 +25,8 @@ let ignoreApiProcessClosed = false;
|
||||
|
||||
let manifestJsonFileName = 'electron.manifest.json';
|
||||
let watchable = false;
|
||||
let development = false;
|
||||
|
||||
if (app.commandLine.hasSwitch('manifest')) {
|
||||
manifestJsonFileName = app.commandLine.getSwitchValue('manifest');
|
||||
};
|
||||
@@ -33,6 +35,10 @@ if (app.commandLine.hasSwitch('watch')) {
|
||||
watchable = true;
|
||||
};
|
||||
|
||||
if (app.commandLine.hasSwitch('development')) {
|
||||
development = true;
|
||||
};
|
||||
|
||||
let currentBinPath = path.join(__dirname.replace('app.asar', ''), 'bin');
|
||||
let manifestJsonFilePath = path.join(currentBinPath, manifestJsonFileName);
|
||||
|
||||
@@ -42,6 +48,10 @@ if (watchable) {
|
||||
manifestJsonFilePath = path.join(currentBinPath, manifestJsonFileName);
|
||||
}
|
||||
|
||||
if (development) {
|
||||
auth = app.commandLine.getSwitchValue("devauth");
|
||||
}
|
||||
|
||||
// handle macOS events for opening the app with a file, etc
|
||||
app.on('will-finish-launching', () => {
|
||||
app.on('open-file', (evt, file) => {
|
||||
@@ -136,19 +146,27 @@ app.on('ready', () => {
|
||||
if (isSplashScreenEnabled()) {
|
||||
startSplashScreen();
|
||||
}
|
||||
// Added default port as configurable for port restricted environments.
|
||||
let defaultElectronPort = 8000;
|
||||
if (manifestJsonFile.electronPort) {
|
||||
defaultElectronPort = (manifestJsonFile.electronPort)
|
||||
if (defaultElectronPort == 'random') {
|
||||
defaultElectronPort = (Math.floor(Math.random() * 2000 + 8000)); //Use random port to reduce risk of race conditions between when we find a free port here, and when the app locks on the port
|
||||
}
|
||||
}
|
||||
// hostname needs to be localhost, otherwise Windows Firewall will be triggered.
|
||||
portscanner.findAPortNotInUse(defaultElectronPort, 65535, 'localhost', function (error, port) {
|
||||
|
||||
if (development) {
|
||||
let port = parseInt(app.commandLine.getSwitchValue('devport'));
|
||||
try { console.log('Electron Socket IO Port: ' + port); } catch { }
|
||||
startSocketApiBridge(port);
|
||||
});
|
||||
} else {
|
||||
|
||||
// Added default port as configurable for port restricted environments.
|
||||
let defaultElectronPort = 8000;
|
||||
if (manifestJsonFile.electronPort) {
|
||||
defaultElectronPort = (manifestJsonFile.electronPort)
|
||||
if (defaultElectronPort == 'random') {
|
||||
defaultElectronPort = (Math.floor(Math.random() * 2000 + 8000)); //Use random port to reduce risk of race conditions between when we find a free port here, and when the app locks on the port
|
||||
}
|
||||
}
|
||||
// hostname needs to be localhost, otherwise Windows Firewall will be triggered.
|
||||
portscanner.findAPortNotInUse(defaultElectronPort, 65535, 'localhost', function (error, port) {
|
||||
try { console.log('Electron Socket IO Port: ' + port); } catch { }
|
||||
startSocketApiBridge(port);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.on('quit', async (event, exitCode) => {
|
||||
@@ -268,8 +286,11 @@ function startSocketApiBridge(port) {
|
||||
server.on('listening', function () {
|
||||
try { console.log('Electron Socket started on port %s at %s', server.address().port, server.address().address); } catch { }
|
||||
// Now that socket connection is established, we can guarantee port will not be open for portscanner
|
||||
|
||||
if (watchable) {
|
||||
startAspCoreBackendWithWatch(port);
|
||||
} else if (development) {
|
||||
//Nothing else to do
|
||||
} else {
|
||||
startAspCoreBackend(port);
|
||||
}
|
||||
@@ -283,7 +304,8 @@ function startSocketApiBridge(port) {
|
||||
let checkReconnectTimeout = 0;
|
||||
// @ts-ignore
|
||||
io.on('connection', (socket) => {
|
||||
|
||||
try { console.log('Socket ' + socket.id + ' connected from .NET'); } catch { }
|
||||
|
||||
isConnected = true;
|
||||
clearTimeout(checkReconnectTimeout);
|
||||
|
||||
@@ -311,7 +333,6 @@ function startSocketApiBridge(port) {
|
||||
});
|
||||
|
||||
socket.on("auth", function (authKey) {
|
||||
|
||||
if (authKey != auth) {
|
||||
throw new Error("Invalid auth key");
|
||||
}
|
||||
|
||||
@@ -2,24 +2,49 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ElectronNET.WebApp
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
IWebHostBuilder builder;
|
||||
|
||||
#if DEBUG
|
||||
var webPort = Electron.Experimental.FreeTcpPort();
|
||||
|
||||
await Electron.Experimental.StartElectronForDevelopment(webPort);
|
||||
|
||||
builder = CreateWebHostBuilder(args);
|
||||
// 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.
|
||||
if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
|
||||
{
|
||||
builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory);
|
||||
}
|
||||
|
||||
builder.UseUrls("http://localhost:" + webPort);
|
||||
#else
|
||||
builder = CreateWebHostBuilder(args);
|
||||
Debugger.Launch();
|
||||
Electron.ReadAuth();
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
builder.UseElectron(args);
|
||||
#endif
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
|
||||
{
|
||||
return WebHost.CreateDefaultBuilder(args)
|
||||
.ConfigureLogging((hostingContext, logging) => { logging.AddConsole(); })
|
||||
.UseElectron(args)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
{
|
||||
"profiles": {
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"launchUrl": "http://localhost:50395/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ASPNETCORE_URLS": "http://localhost:50395/"
|
||||
},
|
||||
"distributionName": ""
|
||||
},
|
||||
"run with electronize": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "$(SolutionDir)ElectronNET.CLI\\bin\\Debug\\net6.0\\dotnet-electronize-h5.exe",
|
||||
"commandLineArgs": "start /from-build-output $(SolutionDir)ElectronNET.WebApp\\bin\\$(Configuration)\\net6.0",
|
||||
"workingDirectory": "$(SolutionDir)ElectronNET.WebApp"
|
||||
},
|
||||
"run from csharp": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:50395/",
|
||||
"hotReloadEnabled": false
|
||||
}
|
||||
},
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
@@ -6,27 +31,5 @@
|
||||
"applicationUrl": "http://localhost:50394/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Electron.NET App": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "$(SolutionDir)ElectronNET.CLI\\bin\\Debug\\net5.0\\dotnet-electronize-h5.exe",
|
||||
"commandLineArgs": "start /from-build-output $(SolutionDir)ElectronNET.WebApp\\bin\\$(Configuration)\\net5.0",
|
||||
"workingDirectory": "$(SolutionDir)ElectronNET.WebApp"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"ElectronNET.WebApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:50395/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ElectronNET.WebApp
|
||||
{
|
||||
@@ -43,11 +44,11 @@ namespace ElectronNET.WebApp
|
||||
|
||||
if (HybridSupport.IsElectronActive)
|
||||
{
|
||||
ElectronBootstrap();
|
||||
Task.Run(() => ElectronBootstrap());
|
||||
}
|
||||
}
|
||||
|
||||
public async void ElectronBootstrap()
|
||||
public async Task ElectronBootstrap()
|
||||
{
|
||||
//AddDevelopmentTests();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user