-
-
Notifications
You must be signed in to change notification settings - Fork 741
Feat: Add Electron.NET startup lifecycle events #941
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
Feat: Add Electron.NET startup lifecycle events #941
Conversation
|
Although this introduces some lifecycle events, I still don’t understand where to place the calls, because even with these events, the protocol API throws an error for being too late. |
…e, removed static OnAppReadyCallback in favor of Events.OnReady
Yeah I think we will need to touch the "main.js" / JS counterpart for it. Not sure yet. Unfortunately, I won't have time today to look at that. Hopefully tomorrow / by the end of the week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is not needed. You can simply do this in the WebApp's Program.cs:
public static void Main(string[] args)
{
ElectronNetRuntime.RuntimeController.WaitStartedTask.ContinueWith(_ =>
{
// Do something before ready state
});
CreateWebHostBuilder(args).Build().Run();
}Or when a more fine-grained state would be needed, that's the intended kind of pattern.
Background
About Statics
From an architectural point of view, having statics in the first place is always a kind of smell, but sometimes - like here - it's more or less unavoidable or rather: the consequences (incompatibility, more complicated setup, etc.) would not weigh out the architectual advantage.
But - when it's gotta be static - then it should really be a single static point of access where everything is anchored to.
The previous code had a number of statics and some pseudo-statics (singletons without static accessor) which were "floating around" and code needed to go sideways and make some extra-turns in order for them to access each other.
The static ElectronNetRuntime class is that single static anchor now and ElectronNetRuntime.RuntimeController is the place and point of access for everything related to lifecycle management.
Why no Events?
The reasons why I used awaitable tasks instead of events are these:
- Single Occurence
Each event happens exactly once - Fixed Sequence
The events do not occur arbitrarily. There's a fixed and guaranteed order - Consumer Convenience
When you look at Main() in Program.cs in the ElectronNET.ConsoleApp demo you can see what I mean:
await runtimeController.Start();
await runtimeController.WaitReadyTask;
await ElectronBootstrap();
await runtimeController.WaitStoppedTask;This allows to write much nicer and clearly structured code than with events.
That being said, if we really need events, they should be exposed by the RuntimeController.
Side Note
An "options" class that is passed as an argument but exposing application lifecycle events is more a kind of hack than a sane pattern. 😉
|
As we are talking about Just wanted to mention it so that you're aware and it doesn't become forgotten. :-) |
While I don’t completely agree with that statement, my approach was inspired by the DI patterns in ASP.NET Core authentication mechanisms, which use an Events property to handle lifecycle callbacks (e.g., ticket or authentication events), if there's already an alternative solution in place, I'm fine with closing this PR. |
|
The example you provided isn’t working because inside public void Initialize()
{
try
{
ElectronNetRuntime.BuildInfo = this.GatherBuildInfo();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
this.CollectProcessData();
this.SetElectronExecutable();
ElectronNetRuntime.StartupMethod = this.DetectAppTypeAndStartup();
Console.WriteLine("Evaluated StartupMethod: " + ElectronNetRuntime.StartupMethod);
if (ElectronNetRuntime.DotnetAppType != DotnetAppType.AspNetCoreApp)
{
ElectronNetRuntime.RuntimeControllerCore = this.CreateRuntimeController();
}
}Even if I remove the |
Okay, let me check... |
|
Alright - don't comment out that condition - this would get the wrong controller. This should work: public static void Main(string[] args)
{
var builder = CreateWebHostBuilder(args);
ElectronNetRuntime.RuntimeController.WaitStartedTask.ContinueWith(_ =>
{
// Do something before ready state
});
builder.Build().Run();
} |
|
Mmh this gives me the same Edit: no even in the develop branch |
|
Sorry - tested this time 😆 public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
var constroller = host.Services.GetService<IElectronNetRuntimeController>();
constroller.WaitStartedTask.ContinueWith(_ =>
{
// Do something before ready state
});
host.Run();
} |
The code above is actually the "DI way", I just forgot that I did it that way for AspNet (doing too many things in parallel). |
…-class-and-improve-di Refactor runtime host initialization
|
We can close this PR as the solution you've provided is working 😁 |
|
Cool, thanks! |
|
@softworkz Even though inside the
|
Yeah I think this is partially due to the socket design here. I think we should abstract that away and queue all messages before the socket is open - then send the queued ones once the connection has been established. |
|
Okay thanks, I'll see what I can do here |
It wasn't clear to me what you want. You had an "onbeforeready" event, that's why I suggested the |
The whole design is not suitable for getting replies late. When the state is "Ready", messages and API calls will be working, not before. Also, when it never gets into ready state, all tasks would be handing or receive timeout exceptions. |
Better not in that direction. Just use the WaitReady event. |
|
Please see #940 to see why all this issue was brought up. In short I wanted to implement the protocol API from electron, but there isn't a suitable time to send the event, early is too early as the socket is not open, later is too late as the app is already in the 'ready' state |
|
Ready means the socket connection is established. Not sure what you want to do before that? Whatever you try - remember that there are 4 modes of operation dotnet-first, electron-first and both in packaged and unpackaged mode. The Ready event/state is the point where things are ready - in all 4 cases. |
|
I understand what you're saying, but in this context “ready” also refers to the Electron app being fully ready. That won’t work for my case because the protocol API needs to be registered before the app reaches the ready state, but at that point, the socket connection still isn’t initialized. If there’s no way to trigger something in between those stages, then I’ll just drop the protocol API implementation entirely. |
|
Regarding 940 - I've just read the headline but haven't had time to look at it, I'm afraid. |
|
Let's say an extra side feature, it's not a problem if it's not possible to implement it, I'll close it as not planned and call it a day, maybe reopen it when (and if) will be possible 😁 |
Assuming you would have plain electron, at which event would this be needed to set up? Or how - if before any event? |
|
From the docs:
The registerSchemesAsPrivileged especially:
|
|
Did you know that you can put a file named It can look like this: module.exports.onStartup = function(host) {
const { app } = require('electron');
console.log('custom_main.js onStartup invoked');
return true;
}There you can call registerSchemesAsPrivileged In the project, you need to specify the target folder like this: <None Update="Assets\custom_main.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>.electron\custom_main.js</TargetPath>
</None> |
|
Ehm no 😅. I'll try this later, thanks for the patience |
Sure, you're very welcome! |
|
Are you interested in |
|
Actually, yes! That was the purpose |
|
The best path is to serve from file:// URLs with "DisableWebSecurity". Everything else gives issues. For example, when registerSchemesAsPrivileged(), you can disable CSP, but it's still like under https, which means that you cannot load images from http: - only https. And there are more stupid limitations. PS: Documentation for |
|
It's not a bit overkill disabling all the security features only for this? If it's the only way I'll do it, but I'm a bit unsure |
It depends on your purpose. If the app also loads web content, then I would think again of course. Though, I should add: When you can get along otherwise, then that's better of course |
|
BTW: Chrome developers don't like this mode (as per source-code comments in Chromium), but it's future proof: They cannot remove it, because the Android WebView works like that and it would break a large share of Android apps 🤣 |
|
Okay if I will ever need to load local media and I don't serve external web content I'll just disable websecurity |
Well, I remember now that I also wasn't sure whether it's meant to stay like this: Originally it was just a quick throw-in to prevent the app from starting when there are still leftover processes running, so that our beta testers know they need to kill them first. After that was addressed, it become unused. Then, recently it became useful again for initializing Sentry (which should happen at a very early stage), dropped that later and few days ago it became useful again for auto-provisioning WebHID API device access permissions (adding that to ElectronNET API would be a future goal). Now you came up with another reasonable use case - so in total, I get to believe that it's a useful feature indeed. |
This PR introduces support for multiple startup/shutdown callbacks to improve initialization control.
Previously, we only had
OnAppReady, which runs too late for some features like protocol registration.Now this supports:
OnBeforeReady→ runs before Electron'sready(useful for protocol setup)OnReady→ same as currentOnAppReadyOnWillQuit→ triggered when shutdown startsOnQuit→ after everything is fully stoppedAlso added:
UseElectronoverload with optionsRuntimeControllerAspNetBaseto call the new eventsThis is an initial implementation and can be adjusted later based on feedback. Let me know if you'd like to change event order, naming, or extend it further.