diff --git a/src/ElectronNET.API/BridgeConnector.cs b/src/ElectronNET.API/BridgeConnector.cs index 23319e5e..a12b1304 100644 --- a/src/ElectronNET.API/BridgeConnector.cs +++ b/src/ElectronNET.API/BridgeConnector.cs @@ -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 { @@ -28,6 +33,82 @@ public static SocketIoFacade Socket return _socket; } + } + + public static async Task GetValueOverSocketAsync(string eventString, string eventCompletedString) + { + CancellationToken cancellationToken = new(); + cancellationToken.ThrowIfCancellationRequested(); + + var taskCompletionSource = new TaskCompletionSource(); + 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() ); + } + 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 GetObjectOverSocketAsync(string eventString, string eventCompletedString) + { + CancellationToken cancellationToken = new(); + cancellationToken.ThrowIfCancellationRequested(); + + var taskCompletionSource = new TaskCompletionSource(); + using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) + { + BridgeConnector.Socket.On(eventCompletedString, (value) => + { + BridgeConnector.Socket.Off(eventCompletedString); + taskCompletionSource.SetResult( ((JObject)value).ToObject() ); + }); + + BridgeConnector.Socket.Emit(eventString); + + return await taskCompletionSource.Task.ConfigureAwait(false); + } + } + + public static async Task GetArrayOverSocketAsync(string eventString, string eventCompletedString) + { + CancellationToken cancellationToken = new(); + cancellationToken.ThrowIfCancellationRequested(); + + var taskCompletionSource = new TaskCompletionSource(); + using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) + { + BridgeConnector.Socket.On(eventCompletedString, (value) => + { + BridgeConnector.Socket.Off(eventCompletedString); + taskCompletionSource.SetResult( ((JArray)value).ToObject() ); + }); + + BridgeConnector.Socket.Emit(eventString); + + return await taskCompletionSource.Task.ConfigureAwait(false); + } } + } } \ No newline at end of file diff --git a/src/ElectronNET.API/Electron.cs b/src/ElectronNET.API/Electron.cs index 23f9902d..5c636164 100644 --- a/src/ElectronNET.API/Electron.cs +++ b/src/ElectronNET.API/Electron.cs @@ -88,5 +88,10 @@ public static class Electron /// Control your app in the macOS dock. /// public static Dock Dock { get { return Dock.Instance; } } + + /// + /// Electeon extensions to the Nodejs process object. + /// + public static Process Process { get { return Process.Instance; } } } } \ No newline at end of file diff --git a/src/ElectronNET.API/Entities/ProcessVersions.cs b/src/ElectronNET.API/Entities/ProcessVersions.cs new file mode 100644 index 00000000..df41db65 --- /dev/null +++ b/src/ElectronNET.API/Entities/ProcessVersions.cs @@ -0,0 +1,10 @@ +namespace ElectronNET.API +{ + /// + /// An object listing the version strings specific to Electron + /// + /// Value representing Chrome's version string + /// Value representing Electron's version string + /// + public record ProcessVersions(string Chrome, string Electron); +} \ No newline at end of file diff --git a/src/ElectronNET.API/Process.cs b/src/ElectronNET.API/Process.cs new file mode 100644 index 00000000..e2217b7c --- /dev/null +++ b/src/ElectronNET.API/Process.cs @@ -0,0 +1,185 @@ +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ElectronNET.API +{ + /// + /// Electron's process object is extended from the Node.js process object. It adds the + /// events, properties, and methods. + /// + 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(); + + /// + /// The process.execPath property returns the absolute pathname of the executable that + /// started the Node.js process. Symbolic links, if any, are resolved. + /// + public Task ExecPathAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-execPath", "process-execPath-Completed"); + } + } + + /// + /// 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 + /// + public Task ArgvAsync + { + get + { + return BridgeConnector.GetArrayOverSocketAsync( + "process-argv", "process-argv-Completed"); + } + } + + /// + /// The process.execPath property returns the absolute pathname of the executable that + /// started the Node.js process. Symbolic links, if any, are resolved. + /// + public Task TypeAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-type", "process-type-Completed"); + } + } + + + /// + /// The process.versions property returns an object listing the version strings of + /// chrome and electron. + /// + public Task VersionsAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-versions", "process-versions-Completed"); + } + } + + + /// + /// 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. + /// + public Task DefaultAppAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-defaultApp", "process-defaultApp-Completed"); + } + } + + /// + /// 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 + /// + public Task IsMainFrameAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-isMainFrame", "process-isMainFrame-Completed"); + } + } + + /// + /// A String representing the path to the resources directory. + /// + public Task ResourcesPathAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-resourcesPath", "process-resourcesPath-Completed"); + } + } + + /// + /// 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. + /// + public Task UpTimeAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-uptime", "process-uptime-Completed"); + } + } + + /// + /// The PID of the electron process + /// + public Task PidAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-pid", "process-pid-Completed"); + } + } + + + /// + /// The operating system CPU architecture for which the Node.js binary was compiled + /// + public Task ArchAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-arch", "process-arch-Completed"); + } + } + + /// + /// A string identifying the operating system platform on which the Node.js process is running + /// + public Task PlatformAsync + { + get + { + return BridgeConnector.GetValueOverSocketAsync( + "process-platform", "process-platform-Completed"); + } + } + + } +} diff --git a/src/ElectronNET.API/ServiceCollectionExtensions.cs b/src/ElectronNET.API/ServiceCollectionExtensions.cs index f933731b..a63aacf2 100644 --- a/src/ElectronNET.API/ServiceCollectionExtensions.cs +++ b/src/ElectronNET.API/ServiceCollectionExtensions.cs @@ -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); } } diff --git a/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs b/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs index 205ff7b4..c0205b6a 100644 --- a/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs +++ b/src/ElectronNET.CLI/Commands/Actions/DeployEmbeddedElectronFiles.cs @@ -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) diff --git a/src/ElectronNET.CLI/ElectronNET.CLI.csproj b/src/ElectronNET.CLI/ElectronNET.CLI.csproj index fd30a574..074ffd87 100644 --- a/src/ElectronNET.CLI/ElectronNET.CLI.csproj +++ b/src/ElectronNET.CLI/ElectronNET.CLI.csproj @@ -68,6 +68,7 @@ + diff --git a/src/ElectronNET.Host/api/process.js b/src/ElectronNET.Host/api/process.js new file mode 100644 index 00000000..36c08b4a --- /dev/null +++ b/src/ElectronNET.Host/api/process.js @@ -0,0 +1,62 @@ +"use strict"; +let electronSocket; +module.exports = (socket) => { + electronSocket = socket; + socket.on('process-execPath', () => { + const value = process.execPath; + electronSocket.emit('process-execPath-Completed', value); + }); + socket.on('process-argv', () => { + const value = process.argv; + electronSocket.emit('process-argv-Completed', value); + }); + socket.on('process-type', () => { + const value = process.type; + electronSocket.emit('process-type-Completed', value); + }); + socket.on('process-versions', () => { + const value = process.versions; + electronSocket.emit('process-versions-Completed', value); + }); + socket.on('process-defaultApp', () => { + if (process.defaultApp === undefined) { + electronSocket.emit('process-defaultApp-Completed', false); + return; + } + electronSocket.emit('process-defaultApp-Completed', process.defaultApp); + }); + socket.on('process-isMainFrame', () => { + if (process.isMainFrame === undefined) { + electronSocket.emit('process-isMainFrame-Completed', false); + return; + } + electronSocket.emit('process-isMainFrame-Completed', process.isMainFrame); + }); + socket.on('process-resourcesPath', () => { + const value = process.resourcesPath; + electronSocket.emit('process-resourcesPath-Completed', value); + }); + socket.on('process-uptime', () => { + let value = process.uptime(); + if (value === undefined) { + value = -1; + } + electronSocket.emit('process-uptime-Completed', value); + }); + socket.on('process-pid', () => { + if (process.pid === undefined) { + electronSocket.emit('process-pid-Completed', -1); + return; + } + electronSocket.emit('process-pid-Completed', process.pid); + }); + socket.on('process-arch', () => { + const value = process.arch; + electronSocket.emit('process-arch-Completed', value); + }); + socket.on('process-platform', () => { + const value = process.platform; + electronSocket.emit('process-platform-Completed', value); + }); +}; +//# sourceMappingURL=process.js.map \ No newline at end of file diff --git a/src/ElectronNET.Host/api/process.js.map b/src/ElectronNET.Host/api/process.js.map new file mode 100644 index 00000000..096946ca --- /dev/null +++ b/src/ElectronNET.Host/api/process.js.map @@ -0,0 +1 @@ +{"version":3,"file":"process.js","sourceRoot":"","sources":["process.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/ElectronNET.Host/api/process.ts b/src/ElectronNET.Host/api/process.ts new file mode 100644 index 00000000..ad04dbf2 --- /dev/null +++ b/src/ElectronNET.Host/api/process.ts @@ -0,0 +1,73 @@ +import { Socket } from 'net'; +let electronSocket; + +export = (socket: Socket) => { + electronSocket = socket; + + socket.on('process-execPath', () => { + const value = process.execPath; + electronSocket.emit('process-execPath-Completed', value); + }); + + socket.on('process-argv', () => { + const value = process.argv; + electronSocket.emit('process-argv-Completed', value); + }); + + socket.on('process-type', () => { + const value = process.type; + electronSocket.emit('process-type-Completed', value); + }); + + socket.on('process-versions', () => { + const value = process.versions; + electronSocket.emit('process-versions-Completed', value); + }); + + socket.on('process-defaultApp', () => { + if (process.defaultApp === undefined) { + electronSocket.emit('process-defaultApp-Completed', false); + return; + } + electronSocket.emit('process-defaultApp-Completed', process.defaultApp); + }); + + socket.on('process-isMainFrame', () => { + if (process.isMainFrame === undefined) { + electronSocket.emit('process-isMainFrame-Completed', false); + return; + } + electronSocket.emit('process-isMainFrame-Completed', process.isMainFrame); + }); + + socket.on('process-resourcesPath', () => { + const value = process.resourcesPath; + electronSocket.emit('process-resourcesPath-Completed', value); + }); + + socket.on('process-uptime', () => { + let value = process.uptime(); + if (value === undefined) { + value = -1; + } + electronSocket.emit('process-uptime-Completed', value); + }); + + socket.on('process-pid', () => { + if (process.pid === undefined) { + electronSocket.emit('process-pid-Completed', -1); + return; + } + electronSocket.emit('process-pid-Completed', process.pid); + }); + + socket.on('process-arch', () => { + const value = process.arch; + electronSocket.emit('process-arch-Completed', value); + }); + + socket.on('process-platform', () => { + const value = process.platform; + electronSocket.emit('process-platform-Completed', value); + }) +}; diff --git a/src/ElectronNET.Host/main.js b/src/ElectronNET.Host/main.js index 622342e0..f90c2d5f 100644 --- a/src/ElectronNET.Host/main.js +++ b/src/ElectronNET.Host/main.js @@ -15,9 +15,31 @@ let nativeTheme; let dock; let launchFile; let launchUrl; +let processApi; let manifestJsonFileName = 'electron.manifest.json'; let watchable = false; + +// handle for opening the app with a file for win and linux +if (process && process.argv.length > 1) { + + let firstAppArgument = process.argv[1]; + + // With invoked via electronize, the first argument is the path to the main.js. + // Per issue #337, the /args switch can also be present. If either are present, + // we need to check the subsequent argument. + if (firstAppArgument === '..\\..\\main.js' || firstAppArgument === '../../main.js' || firstAppArgument === '/args') { + if (process.argv.length > 2) { + firstAppArgument = process.argv[2]; + } + } + + // only append the first app arg if it is not already a switch + if (!firstAppArgument.startsWith("--")) { + app.commandLine.appendSwitch("open-file", firstAppArgument); + } +} + if (app.commandLine.hasSwitch('manifest')) { manifestJsonFileName = app.commandLine.getSwitchValue('manifest'); } @@ -229,6 +251,7 @@ function startSocketApiBridge(port) { if (powerMonitor === undefined) powerMonitor = require('./api/powerMonitor')(socket); if (nativeTheme === undefined) nativeTheme = require('./api/nativeTheme')(socket); if (dock === undefined) dock = require('./api/dock')(socket); + if (processApi === undefined) processApi = require('./api/process')(socket); socket.on('register-app-open-file-event', (id) => { global['electronsocket'] = socket;