postit: traceable OIDC login UX + session persistence + 2nd-instance early exit
- OidcLoginPhase enum + IProgress<OidcLoginPhase> on YavscApiClient.LoginInteractiveAsync, surfaced in the UI as PhaseLabel (FR). Lets operators see where the flow actually stalls, in particular whether the postit://callback ever arrives on the running instance. - YavscApiClient.TrySilentLoginAsync: silent refresh at boot. Returns false (and purges the store) when the refresh token is rejected by the OP. - App.OnFrameworkInitializationCompleted auto-routes: HomePage is the navigation root; on Opened the app calls TrySilentLoginAsync and pushes MainPage if a session is restored. Logout pops back to HomePage via the new persistent SessionStatusBanner (Connecté / Déconnecté + Logout button). - PostIt.Desktop.Program.Main now detects the postit://callback URL BEFORE Avalonia boots, hands it off via SingleInstance, and exits. Stops the 2nd PostIt instance from flashing its own MainWindow while the 1st instance is still waiting on the named pipe. The check in App.OnFrameworkInitializationCompleted is kept as belt-and-braces defence-in-depth. - SchemeUrlDetector: pure platform-independent detector extracted for unit testing. - Tests: 7 new SchemeUrlDetectorTests + 5 new phase / silent refresh tests in YavscApiClientTests.
This commit is contained in:
parent
ee2c8452ac
commit
514549c5f9
14 changed files with 804 additions and 42 deletions
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Avalonia;
|
||||
using PostIt.Services;
|
||||
|
||||
namespace PostIt.Desktop;
|
||||
|
||||
|
|
@ -12,10 +14,57 @@ sealed class Program
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
PlatformBootstrap.EnsureInitialized();
|
||||
|
||||
// Short-circuit 2nd-instance launches (OS handing us the
|
||||
// postit://callback URL) BEFORE Avalonia spins up a window.
|
||||
// If we let Avalonia initialise, the new MainWindow flashes
|
||||
// open for a frame before OnFrameworkInitializationCompleted
|
||||
// detects the scheme and shuts down — visible to the user as
|
||||
// a second window with "Déconnecté" while the original
|
||||
// instance is still waiting on the named pipe.
|
||||
//
|
||||
// We only need the scheme prefix and the OS-supplied URL,
|
||||
// both of which are plain System.* / PostIt.Services —
|
||||
// nothing Avalonia-specific is touched here, so the rule
|
||||
// above is not violated.
|
||||
if (TryHandOffCustomSchemeUrl(args)) return;
|
||||
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect a 2nd-instance launch (OS dispatching the postit:// URL
|
||||
/// after the user completed login in the system browser), forward
|
||||
/// the URL to the running instance over the named pipe, and exit
|
||||
/// before Avalonia can open a window. Returns true when the
|
||||
/// process should terminate without booting Avalonia.
|
||||
/// </summary>
|
||||
private static bool TryHandOffCustomSchemeUrl(string[] args)
|
||||
{
|
||||
var url = SchemeUrlDetector.FindCallbackUrl(args);
|
||||
if (url is null) return false;
|
||||
|
||||
// Best-effort: try to send the URL to the running
|
||||
// instance via the named pipe. If the pipe isn't
|
||||
// answering (user double-clicked the link after closing
|
||||
// PostIt), there's no 1st instance to forward to — we
|
||||
// exit cleanly anyway rather than booting a stray
|
||||
// PostIt window that would just confuse the user.
|
||||
try
|
||||
{
|
||||
SingleInstance.TryHandOffAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Pipe errors are non-fatal for the 2nd-instance
|
||||
// hand-off — we still want to exit cleanly.
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue