Skip to content

Reintroduce #648 #881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 22, 2025
Merged
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
83 changes: 82 additions & 1 deletion src/ElectronNET.API/BridgeConnector.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
namespace ElectronNET.API
using System;
using Newtonsoft.Json.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ElectronNET.API
{
internal static class BridgeConnector
{
Expand Down Expand Up @@ -28,6 +33,82 @@ public static SocketIoFacade Socket

return _socket;
}
}

public static async Task<T> GetValueOverSocketAsync<T>(string eventString, string eventCompletedString)
{
CancellationToken cancellationToken = new();
cancellationToken.ThrowIfCancellationRequested();

var taskCompletionSource = new TaskCompletionSource<T>();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.On(eventCompletedString, (value) =>
{
BridgeConnector.Socket.Off(eventCompletedString);

if (value == null)
{
Console.WriteLine($"ERROR: BridgeConnector (event: '{eventString}') returned null. Socket loop hang.");
taskCompletionSource.SetCanceled();
return;
}

try
{
taskCompletionSource.SetResult( new JValue(value).ToObject<T>() );
}
catch (Exception e)
{
Console.WriteLine($"ERROR: BridgeConnector (event: '{eventString}') exception: {e.Message}. Socket loop hung.");
}
});

BridgeConnector.Socket.Emit(eventString);

return await taskCompletionSource.Task.ConfigureAwait(false);
}
}

public static async Task<T> GetObjectOverSocketAsync<T>(string eventString, string eventCompletedString)
{
CancellationToken cancellationToken = new();
cancellationToken.ThrowIfCancellationRequested();

var taskCompletionSource = new TaskCompletionSource<T>();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.On(eventCompletedString, (value) =>
{
BridgeConnector.Socket.Off(eventCompletedString);
taskCompletionSource.SetResult( ((JObject)value).ToObject<T>() );
});

BridgeConnector.Socket.Emit(eventString);

return await taskCompletionSource.Task.ConfigureAwait(false);
}
}

public static async Task<T> GetArrayOverSocketAsync<T>(string eventString, string eventCompletedString)
{
CancellationToken cancellationToken = new();
cancellationToken.ThrowIfCancellationRequested();

var taskCompletionSource = new TaskCompletionSource<T>();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.On(eventCompletedString, (value) =>
{
BridgeConnector.Socket.Off(eventCompletedString);
taskCompletionSource.SetResult( ((JArray)value).ToObject<T>() );
});

BridgeConnector.Socket.Emit(eventString);

return await taskCompletionSource.Task.ConfigureAwait(false);
}
}

}
}
5 changes: 5 additions & 0 deletions src/ElectronNET.API/Electron.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,10 @@ public static class Electron
/// Control your app in the macOS dock.
/// </summary>
public static Dock Dock { get { return Dock.Instance; } }

/// <summary>
/// Electeon extensions to the Nodejs process object.
/// </summary>
public static Process Process { get { return Process.Instance; } }
}
}
10 changes: 10 additions & 0 deletions src/ElectronNET.API/Entities/ProcessVersions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ElectronNET.API
{
/// <summary>
/// An object listing the version strings specific to Electron
/// </summary>
/// <param name="Chrome">Value representing Chrome's version string</param>
/// <param name="Electron">Value representing Electron's version string</param>
/// <returns></returns>
public record ProcessVersions(string Chrome, string Electron);
}
185 changes: 185 additions & 0 deletions src/ElectronNET.API/Process.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace ElectronNET.API
{
/// <summary>
/// Electron's process object is extended from the Node.js process object. It adds the
/// events, properties, and methods.
/// </summary>
public sealed class Process
{
internal Process() { }

internal static Process Instance
{
get
{
if (_process == null)
{
lock (_syncRoot)
{
if (_process == null)
{
_process = new Process();
}
}
}

return _process;
}
}

private static Process _process;

private static readonly object _syncRoot = new();

/// <summary>
/// The process.execPath property returns the absolute pathname of the executable that
/// started the Node.js process. Symbolic links, if any, are resolved.
/// </summary>
public Task<string> ExecPathAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<string>(
"process-execPath", "process-execPath-Completed");
}
}

/// <summary>
/// The process.argv property returns an array containing the command-line arguments passed
/// when the Node.js process was launched. The first element will be process.execPath. See
/// process.argv0 if access to the original value of argv[0] is needed. The second element
/// will be the path to the JavaScript file being executed. The remaining elements will be
/// any additional command-line arguments
/// </summary>
public Task<string[]> ArgvAsync
{
get
{
return BridgeConnector.GetArrayOverSocketAsync<string[]>(
"process-argv", "process-argv-Completed");
}
}

/// <summary>
/// The process.execPath property returns the absolute pathname of the executable that
/// started the Node.js process. Symbolic links, if any, are resolved.
/// </summary>
public Task<string> TypeAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<string>(
"process-type", "process-type-Completed");
}
}


/// <summary>
/// The process.versions property returns an object listing the version strings of
/// chrome and electron.
/// </summary>
public Task<ProcessVersions> VersionsAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<ProcessVersions>(
"process-versions", "process-versions-Completed");
}
}


/// <summary>
/// A Boolean. When app is started by being passed as parameter to the default app, this
/// property is true in the main process, otherwise it is false.
/// </summary>
public Task<bool> DefaultAppAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<bool>(
"process-defaultApp", "process-defaultApp-Completed");
}
}

/// <summary>
/// A Boolean, true when the current renderer context is the "main" renderer frame. If you
/// want the ID of the current frame you should use webFrame.routingId
/// </summary>
public Task<bool> IsMainFrameAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<bool>(
"process-isMainFrame", "process-isMainFrame-Completed");
}
}

/// <summary>
/// A String representing the path to the resources directory.
/// </summary>
public Task<string> ResourcesPathAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<string>(
"process-resourcesPath", "process-resourcesPath-Completed");
}
}

/// <summary>
/// The number of seconds the current Node.js process has been running. The return value
/// includes fractions of a second. Use Math.floor() to get whole seconds.
/// </summary>
public Task<double> UpTimeAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<double>(
"process-uptime", "process-uptime-Completed");
}
}

/// <summary>
/// The PID of the electron process
/// </summary>
public Task<int> PidAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<int>(
"process-pid", "process-pid-Completed");
}
}


/// <summary>
/// The operating system CPU architecture for which the Node.js binary was compiled
/// </summary>
public Task<string> ArchAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<string>(
"process-arch", "process-arch-Completed");
}
}

/// <summary>
/// A string identifying the operating system platform on which the Node.js process is running
/// </summary>
public Task<string> PlatformAsync
{
get
{
return BridgeConnector.GetValueOverSocketAsync<string>(
"process-platform", "process-platform-Completed");
}
}

}
}
3 changes: 2 additions & 1 deletion src/ElectronNET.API/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static IServiceCollection AddElectron(this IServiceCollection services)
.AddSingleton(provider => HostHook.Instance)
.AddSingleton(provider => PowerMonitor.Instance)
.AddSingleton(provider => NativeTheme.Instance)
.AddSingleton(provider => Dock.Instance);
.AddSingleton(provider => Dock.Instance)
.AddSingleton(provider => Process.Instance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public static void Do(string tempPath)
EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "browserView.js", "api.");
EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "powerMonitor.js", "api.");
EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "nativeTheme.js", "api.");
EmbeddedFileHelper.DeployEmbeddedFile(hostApiFolder, "process.js", "api.");

string splashscreenFolder = Path.Combine(tempPath, "splashscreen");
if (Directory.Exists(splashscreenFolder) == false)
Expand Down
1 change: 1 addition & 0 deletions src/ElectronNET.CLI/ElectronNET.CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<EmbeddedResource Include="..\ElectronNET.Host\api\browserView.js" Link="ElectronHost\api\browserView.js" />
<EmbeddedResource Include="..\ElectronNET.Host\api\powerMonitor.js" Link="ElectronHost\api\powerMonitor.js" />
<EmbeddedResource Include="..\ElectronNET.Host\api\nativeTheme.js" Link="ElectronHost\api\nativeTheme.js" />
<EmbeddedResource Include="..\ElectronNET.Host\api\process.js" Link="ElectronHost\api\process.js" />
<EmbeddedResource Include="..\ElectronNET.Host\.vscode\launch.json" Link="ElectronHost\.vscode\launch.json" />
<EmbeddedResource Include="..\ElectronNET.Host\.vscode\tasks.json" Link="ElectronHost\.vscode\tasks.json" />
</ItemGroup>
Expand Down
62 changes: 62 additions & 0 deletions src/ElectronNET.Host/api/process.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/ElectronNET.Host/api/process.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading