Merge pull request #1707 from rmcrackan/rmcrackan/webview-err-msg

better error messages when webview is the problem
This commit is contained in:
rmcrackan
2026-03-30 14:22:51 -04:00
committed by GitHub
7 changed files with 185 additions and 53 deletions

View File

@@ -5,6 +5,7 @@ using Avalonia.Platform;
using Avalonia.Threading;
using Dinah.Core;
using LibationFileManager;
using LibationUiBase;
using LibationUiBase.Forms;
using System;
using System.Threading.Tasks;
@@ -29,8 +30,22 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager
{
try
{
if (Configuration.Instance.UseWebView && await BrowserLoginAsync(choiceIn) is ChoiceOut external)
return external;
if (Configuration.Instance.UseWebView)
{
try
{
if (await BrowserLoginAsync(choiceIn) is ChoiceOut external)
return external;
}
catch (Exception ex) when (WebView2LoginErrorMessage.IsWebView2SignInInfrastructureFailure(ex))
{
await MessageBox.ShowAdminAlert(
App.MainWindow,
WebView2LoginErrorMessage.ExplainerBody,
WebView2LoginErrorMessage.Caption,
ex);
}
}
}
catch (Exception ex)
{
@@ -44,19 +59,6 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager
}
private async Task<ChoiceOut?> BrowserLoginAsync(ChoiceIn shoiceIn)
{
try
{
return await BrowserLoginAsyncCore(shoiceIn);
}
catch (Exception ex)
{
Serilog.Log.Logger.Warning(ex, "In-app browser failed; falling back to external browser");
return null;
}
}
private async Task<ChoiceOut?> BrowserLoginAsyncCore(ChoiceIn shoiceIn)
{
TaskCompletionSource<ChoiceOut?> tcs = new();

View File

@@ -3,6 +3,7 @@ using AudibleUtilities;
using Avalonia.Controls;
using Avalonia.Input;
using LibationFileManager;
using LibationUiBase;
using LibationUiBase.Forms;
using ReactiveUI;
using System;
@@ -221,11 +222,22 @@ public partial class MainVM
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(
MainWindow,
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
"Error importing library",
ex);
if (WebView2LoginErrorMessage.TryFindInTree(ex, out var webViewEx) && webViewEx is not null)
{
await MessageBox.ShowAdminAlert(
MainWindow,
WebView2LoginErrorMessage.ExplainerBody,
WebView2LoginErrorMessage.Caption,
webViewEx);
}
else
{
await MessageBox.ShowAdminAlert(
MainWindow,
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
"Error importing library",
ex);
}
}
}

View File

@@ -6,6 +6,7 @@ using Avalonia.Threading;
using DataLayer;
using Dinah.Core.Collections.Generic;
using LibationFileManager;
using LibationUiBase;
using LibationUiBase.Forms;
using LibationUiBase.GridView;
using ReactiveUI;
@@ -434,11 +435,22 @@ public class ProductsDisplayViewModel : ViewModelBase
}
catch (Exception ex)
{
await MessageBox.ShowAdminAlert(
null,
"Error scanning library. You may still manually select books to remove from Libation's library.",
"Error scanning library",
ex);
if (WebView2LoginErrorMessage.TryFindInTree(ex, out var webViewEx) && webViewEx is not null)
{
await MessageBox.ShowAdminAlert(
null,
WebView2LoginErrorMessage.ExplainerBody,
WebView2LoginErrorMessage.Caption,
webViewEx);
}
else
{
await MessageBox.ShowAdminAlert(
null,
"Error scanning library. You may still manually select books to remove from Libation's library.",
"Error scanning library",
ex);
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Runtime.InteropServices;
namespace LibationUiBase;
/// <summary>
/// User-facing copy and exception matching when embedded sign-in browser fails (often mistaken for a "library scan" bug).
/// Shared by WinForms and Avalonia; stack markers include WebView2 and Avalonia's NativeWebDialog.
/// </summary>
public static class WebView2LoginErrorMessage
{
public const string Caption = "Sign-in browser could not start";
public static string ExplainerBody =>
"Libation could not start the in-app sign-in browser. On Windows this uses Microsoft WebView2. "
+ "This is a local sign-in or system setup issue — not a failure of the library scan itself.\r\n\r\n"
+ "Things to try:\r\n"
+ "• On Windows: install or repair the Microsoft Edge WebView2 Runtime from https://developer.microsoft.com/microsoft-edge/webview2/\r\n"
+ "• In Libation Settings, try turning off the option to use the embedded browser and use external browser sign-in instead.\r\n"
+ "• Check that security software is not blocking Libation or the embedded browser.\r\n"
+ "• Ensure your account can write to local app data folders (permissions).\r\n"
+ "• If you run as Administrator, try running Libation as a normal user (or the reverse).\r\n\r\n"
+ "After sign-in works, use Import again to scan your library.";
public static bool IsWebView2SignInInfrastructureFailure(Exception ex)
{
for (var e = ex; e is not null; e = e.InnerException)
{
if (!StackMentionsEmbeddedSignInBrowser(e))
continue;
if (e is UnauthorizedAccessException)
return true;
if (e is COMException com
&& (com.HResult == unchecked((int)0x8000FFFF) || com.HResult == unchecked((int)0x80070005)))
return true;
}
return false;
}
public static bool TryFindInTree(Exception ex, out Exception? match)
{
Exception? found = null;
walk(ex);
match = found;
return found is not null;
void walk(Exception? e)
{
if (e is null || found is not null)
return;
if (IsWebView2SignInInfrastructureFailure(e))
found = e;
if (found is not null)
return;
if (e is AggregateException agg)
{
foreach (var inner in agg.InnerExceptions)
walk(inner);
}
walk(e.InnerException);
}
}
private static bool StackMentionsEmbeddedSignInBrowser(Exception e)
{
var stack = e.StackTrace;
return stack is not null
&& (stack.Contains("WebView2", StringComparison.Ordinal)
|| stack.Contains("CoreWebView2", StringComparison.Ordinal)
|| stack.Contains("NativeWebDialog", StringComparison.Ordinal));
}
}

View File

@@ -1,8 +1,10 @@
using AudibleApi;
using Dinah.Core;
using LibationUiBase;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LibationWinForms.Login;
@@ -30,34 +32,48 @@ public partial class WebLoginDialog : Form
ArgumentValidator.EnsureNotNullOrWhiteSpace(choiceIn?.LoginUrl, nameof(choiceIn));
this.Load += async (_, _) =>
{
//enable private browsing
var env = await CoreWebView2Environment.CreateAsync();
var options = env.CreateCoreWebView2ControllerOptions();
options.IsInPrivateModeEnabled = true;
await webView.EnsureCoreWebView2Async(env, options);
webView.CoreWebView2.Settings.UserAgent = Resources.User_Agent;
//Load init cookies
foreach (System.Net.Cookie cookie in choiceIn.SignInCookies ?? [])
try
{
if (string.IsNullOrEmpty(cookie.Value))
continue;
try
{
webView.CoreWebView2.CookieManager.AddOrUpdateCookie(webView.CoreWebView2.CookieManager.CreateCookieWithSystemNetCookie(cookie));
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"Failed to set cookie {cookie.Name} for domain {cookie.Domain}");
}
await initWebViewAndNavigateAsync(choiceIn);
}
catch (Exception ex) when (WebView2LoginErrorMessage.IsWebView2SignInInfrastructureFailure(ex))
{
MessageBoxLib.ShowAdminAlert(this, WebView2LoginErrorMessage.ExplainerBody, WebView2LoginErrorMessage.Caption, ex);
DialogResult = DialogResult.Cancel;
Close();
}
webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
Invoke(() => webView.Source = new Uri(choiceIn.LoginUrl));
};
}
private async Task initWebViewAndNavigateAsync(ChoiceIn choiceIn)
{
// enable private browsing
var env = await CoreWebView2Environment.CreateAsync();
var options = env.CreateCoreWebView2ControllerOptions();
options.IsInPrivateModeEnabled = true;
await webView.EnsureCoreWebView2Async(env, options);
webView.CoreWebView2.Settings.UserAgent = Resources.User_Agent;
// Load init cookies
foreach (System.Net.Cookie cookie in choiceIn.SignInCookies ?? [])
{
if (string.IsNullOrEmpty(cookie.Value))
continue;
try
{
webView.CoreWebView2.CookieManager.AddOrUpdateCookie(webView.CoreWebView2.CookieManager.CreateCookieWithSystemNetCookie(cookie));
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"Failed to set cookie {cookie.Name} for domain {cookie.Domain}");
}
}
webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
Invoke(() => webView.Source = new Uri(choiceIn.LoginUrl));
}
private void WebView_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e)
{
if (e.Uri.Contains("/ap/maplanding") is true)

View File

@@ -1,6 +1,7 @@
using AudibleApi;
using AudibleUtilities;
using LibationFileManager;
using LibationUiBase;
using LibationWinForms.Dialogs.Login;
using System;
using System.Threading.Tasks;
@@ -36,6 +37,8 @@ public class WinformLoginChoiceEager : ILoginChoiceEager
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"Failed to run {nameof(WebLoginDialog)}");
if (WebView2LoginErrorMessage.IsWebView2SignInInfrastructureFailure(ex))
MessageBoxLib.ShowAdminAlert(Owner, WebView2LoginErrorMessage.ExplainerBody, WebView2LoginErrorMessage.Caption, ex);
}
}

View File

@@ -1,6 +1,7 @@
using ApplicationServices;
using AudibleUtilities;
using LibationFileManager;
using LibationUiBase;
using LibationWinForms.Dialogs;
using System;
using System.Collections.Generic;
@@ -87,11 +88,22 @@ public partial class Form1
}
catch (Exception ex)
{
MessageBoxLib.ShowAdminAlert(
this,
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
"Error importing library",
ex);
if (WebView2LoginErrorMessage.TryFindInTree(ex, out var webViewEx) && webViewEx is not null)
{
MessageBoxLib.ShowAdminAlert(
this,
WebView2LoginErrorMessage.ExplainerBody,
WebView2LoginErrorMessage.Caption,
webViewEx);
}
else
{
MessageBoxLib.ShowAdminAlert(
this,
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
"Error importing library",
ex);
}
}
}