11namespace 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 ;
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 )
0 commit comments