mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-24 08:25:30 -04:00
Merge pull request #1707 from rmcrackan/rmcrackan/webview-err-msg
better error messages when webview is the problem
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
75
Source/LibationUiBase/WebView2LoginErrorMessage.cs
Normal file
75
Source/LibationUiBase/WebView2LoginErrorMessage.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user