Skip to content

Commit 979b25f

Browse files
authored
Exclude file-system based transfer from #1038 (#1064)
1 parent fe939e0 commit 979b25f

3 files changed

Lines changed: 28 additions & 149 deletions

File tree

src/ElectronNET.API/Common/ProcessRunner.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace ElectronNET.Common
22
{
33
using System;
4-
using System.Collections.Generic;
54
using System.Diagnostics;
65
using System.Diagnostics.CodeAnalysis;
76
using System.Text;
@@ -112,11 +111,6 @@ public string StandardError
112111
public int? LastExitCode { get; private set; }
113112

114113
public bool Run(string exeFileName, string commandLineArgs, string workingDirectory)
115-
{
116-
return this.Run(exeFileName, commandLineArgs, workingDirectory, null);
117-
}
118-
119-
public bool Run(string exeFileName, string commandLineArgs, string workingDirectory, IDictionary<string, string> environmentVariables)
120114
{
121115
this.CommandLine = commandLineArgs;
122116
this.WorkingFolder = workingDirectory;
@@ -134,14 +128,6 @@ public bool Run(string exeFileName, string commandLineArgs, string workingDirect
134128
WorkingDirectory = workingDirectory
135129
};
136130

137-
if (environmentVariables != null)
138-
{
139-
foreach (var kv in environmentVariables)
140-
{
141-
startInfo.EnvironmentVariables[kv.Key] = kv.Value;
142-
}
143-
}
144-
145131
return this.Run(startInfo);
146132
}
147133

src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs

Lines changed: 24 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
namespace ElectronNET.Runtime.Services.ElectronProcess
22
{
33
using System;
4-
using System.Collections.Generic;
54
using System.ComponentModel;
65
using System.Diagnostics;
76
using System.IO;
87
using System.Linq;
98
using System.Runtime.InteropServices;
10-
using System.Security.Cryptography;
11-
using System.Text.Json;
129
using System.Threading;
1310
using System.Threading.Tasks;
1411
using ElectronNET.Common;
@@ -20,8 +17,7 @@
2017
[Localizable(false)]
2118
internal class ElectronProcessActive : ElectronProcessBase
2219
{
23-
private const string AuthTokenEnvVar = "ELECTRONNET_AUTH_TOKEN";
24-
private const string StartupInfoEnvVar = "ELECTRONNET_STARTUP_INFO";
20+
private readonly Regex extractor = new Regex("^Electron Socket: listening on port (\\d+) at .* using ([a-f0-9]+)$");
2521

2622
private readonly bool isUnpackaged;
2723
private readonly string electronBinaryName;
@@ -96,23 +92,8 @@ protected override async Task StartCore()
9692
workingDir = dir.FullName;
9793
}
9894

99-
// Generate the auth token on the .NET side (256 bit entropy) and pass it
100-
// to Electron via an environment variable. Electron will report the
101-
// OS-selected port via a temporary handshake file - this avoids any
102-
// dependency on parsing Electron's console output.
103-
var authToken = CreateAuthToken();
104-
var startupInfoPath = Path.Combine(
105-
Path.GetTempPath(),
106-
$"electronnet-startup-{Environment.ProcessId}-{Guid.NewGuid():N}.json");
107-
10895
// We don't await this in order to let the state transition to "Starting"
109-
Task.Run(async () => await this.StartInternal(startCmd, args, workingDir, authToken, startupInfoPath).ConfigureAwait(false));
110-
}
111-
112-
private static string CreateAuthToken()
113-
{
114-
var bytes = RandomNumberGenerator.GetBytes(32);
115-
return Convert.ToHexString(bytes).ToLowerInvariant();
96+
Task.Run(async () => await this.StartInternal(startCmd, args, workingDir).ConfigureAwait(false));
11697
}
11798

11899
private void CheckRuntimeIdentifier()
@@ -177,7 +158,7 @@ protected override Task StopCore()
177158
return Task.CompletedTask;
178159
}
179160

180-
private async Task StartInternal(string startCmd, string args, string directoriy, string authToken, string startupInfoPath)
161+
private async Task StartInternal(string startCmd, string args, string directory)
181162
{
182163
var tcs = new TaskCompletionSource();
183164
using var cts = new CancellationTokenSource(2 * 60_000); // cancel after 2 minutes
@@ -189,6 +170,23 @@ private async Task StartInternal(string startCmd, string args, string directoriy
189170
tcs.TrySetResult();
190171
});
191172

173+
void Read_SocketIO_Parameters(object sender, string line)
174+
{
175+
// Look for "Electron Socket: listening on port %s at ..."
176+
var match = extractor.Match(line);
177+
178+
if (match?.Success ?? false)
179+
{
180+
var port = int.Parse(match.Groups[1].Value);
181+
var token = match.Groups[2].Value;
182+
183+
this.process.LineReceived -= Read_SocketIO_Parameters;
184+
ElectronNetRuntime.ElectronAuthToken = token;
185+
ElectronNetRuntime.ElectronSocketPort = port;
186+
tcs.SetResult();
187+
}
188+
}
189+
192190
void Monitor_SocketIO_Failure(object sender, EventArgs e)
193191
{
194192
// We don't want to raise exceptions here - just pass the barrier
@@ -209,24 +207,10 @@ void Monitor_SocketIO_Failure(object sender, EventArgs e)
209207

210208
this.process = new ProcessRunner("ElectronRunner");
211209
this.process.ProcessExited += Monitor_SocketIO_Failure;
210+
this.process.LineReceived += Read_SocketIO_Parameters;
211+
this.process.Run(startCmd, args, directoriy);
212212

213-
var env = new Dictionary<string, string>
214-
{
215-
[AuthTokenEnvVar] = authToken,
216-
[StartupInfoEnvVar] = startupInfoPath,
217-
};
218-
219-
this.process.Run(startCmd, args, directoriy, env);
220-
221-
// Wait for Electron to write the startup-info file (or for the process to die / timeout).
222-
var waitTask = WaitForStartupInfoAsync(startupInfoPath, cts.Token);
223-
var completed = await Task.WhenAny(waitTask, tcs.Task).ConfigureAwait(false);
224-
225-
int port = 0;
226-
if (completed == waitTask && waitTask.Status == TaskStatus.RanToCompletion)
227-
{
228-
port = waitTask.Result;
229-
}
213+
await tcs.Task.ConfigureAwait(false);
230214

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

@@ -237,16 +221,9 @@ void Monitor_SocketIO_Failure(object sender, EventArgs e)
237221

238222
Task.Run(() => this.TransitionState(LifetimeState.Stopped));
239223
}
240-
else if (port > 0)
241-
{
242-
ElectronNetRuntime.ElectronAuthToken = authToken;
243-
ElectronNetRuntime.ElectronSocketPort = port;
244-
this.TransitionState(LifetimeState.Ready);
245-
}
246224
else
247225
{
248-
Console.Error.WriteLine("[StartInternal]: Did not receive Electron startup info before process exit/timeout.");
249-
Task.Run(() => this.TransitionState(LifetimeState.Stopped));
226+
this.TransitionState(LifetimeState.Ready);
250227
}
251228
}
252229
catch (Exception ex)
@@ -256,63 +233,6 @@ void Monitor_SocketIO_Failure(object sender, EventArgs e)
256233
Console.Error.WriteLine("[StartInternal]: Exception: " + ex);
257234
throw;
258235
}
259-
finally
260-
{
261-
try
262-
{
263-
if (File.Exists(startupInfoPath))
264-
{
265-
File.Delete(startupInfoPath);
266-
}
267-
}
268-
catch
269-
{
270-
// best effort cleanup
271-
}
272-
}
273-
}
274-
275-
private static async Task<int> WaitForStartupInfoAsync(string startupInfoPath, CancellationToken cancellationToken)
276-
{
277-
while (!cancellationToken.IsCancellationRequested)
278-
{
279-
try
280-
{
281-
if (File.Exists(startupInfoPath))
282-
{
283-
var json = await File.ReadAllTextAsync(startupInfoPath, cancellationToken).ConfigureAwait(false);
284-
if (!string.IsNullOrWhiteSpace(json))
285-
{
286-
using var doc = JsonDocument.Parse(json);
287-
if (doc.RootElement.TryGetProperty("port", out var portElement) &&
288-
portElement.TryGetInt32(out var port) &&
289-
port > 0)
290-
{
291-
return port;
292-
}
293-
}
294-
}
295-
}
296-
catch (JsonException)
297-
{
298-
// File may be partially written / racing with the writer - retry.
299-
}
300-
catch (IOException)
301-
{
302-
// Same - transient races on file access; retry.
303-
}
304-
305-
try
306-
{
307-
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
308-
}
309-
catch (TaskCanceledException)
310-
{
311-
break;
312-
}
313-
}
314-
315-
return 0;
316236
}
317237

318238
private void Process_Exited(object sender, EventArgs e)

src/ElectronNET.Host/main.js

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,7 @@ let unpackeddotnet = false;
2727
let dotnetpacked = false;
2828
let electronforcedport;
2929
let electronUrl;
30-
// Auth token: prefer the value provided by the .NET host via environment variable
31-
// (dotnet-first startup). Fall back to a freshly generated token so Electron can
32-
// still be launched stand-alone (e.g. for debugging).
33-
let authToken = process.env.ELECTRONNET_AUTH_TOKEN || randomUUID().split('-').join('');
34-
// Path to a temporary handshake file. When set by the .NET host, Electron writes
35-
// the OS-selected socket port into this file so .NET does not have to parse the
36-
// console output.
37-
const startupInfoPath = process.env.ELECTRONNET_STARTUP_INFO;
30+
let authToken = randomUUID().split('-').join('');
3831

3932
if (app.commandLine.hasSwitch('manifest')) {
4033
manifestJsonFileName = app.commandLine.getSwitchValue('manifest');
@@ -281,25 +274,7 @@ function startSocketApiBridge(port) {
281274
server.listen(port, host);
282275
server.on('listening', function () {
283276
const addr = server.address();
284-
console.info(`Electron Socket: listening on port ${addr.port} at ${addr.address}`);
285-
286-
// If the .NET host requested a startup-info handshake, write the selected
287-
// port atomically (tmp + rename) so .NET can pick it up without parsing
288-
// our console output. The auth token is intentionally NOT written to disk
289-
// - the .NET host already knows it (it generated it).
290-
if (startupInfoPath) {
291-
try {
292-
const payload = JSON.stringify({ port: addr.port, pid: process.pid });
293-
const tmp = `${startupInfoPath}.tmp`;
294-
const writeOptions = platform() === 'win32'
295-
? { encoding: 'utf8' }
296-
: { encoding: 'utf8', mode: 0o600 };
297-
fs.writeFileSync(tmp, payload, writeOptions);
298-
fs.renameSync(tmp, startupInfoPath);
299-
} catch (err) {
300-
console.error('Failed to write Electron startup info file:', err);
301-
}
302-
}
277+
console.info(`Electron Socket: listening on port ${addr.port} at ${addr.address} using ${authToken}`);
303278

304279
// Now that socket connection is established, we can guarantee port will not be open for portscanner
305280
if (unpackedelectron) {
@@ -431,8 +406,7 @@ function startAspCoreBackend(electronPort) {
431406

432407
let binFilePath = path.join(currentBinPath, binaryFile);
433408
var options = { cwd: currentBinPath };
434-
// Do not log the parameters: they include the auth token.
435-
console.debug('Starting backend.');
409+
console.debug('Starting backend with parameters:', parameters.join(' '));
436410
apiProcess = cProcess(binFilePath, parameters, options);
437411

438412
apiProcess.stdout.on('data', (data) => {
@@ -462,8 +436,7 @@ function startAspCoreBackendUnpackaged(electronPort) {
462436

463437
let binFilePath = path.join(currentBinPath, binaryFile);
464438
var options = { cwd: currentBinPath };
465-
// Do not log the parameters: they include the auth token.
466-
console.debug('Starting backend (unpackaged).');
439+
console.debug('Starting backend (unpackaged) with parameters:', parameters.join(' '));
467440
apiProcess = cProcess(binFilePath, parameters, options);
468441

469442
apiProcess.stdout.on('data', (data) => {

0 commit comments

Comments
 (0)