Use WebLoginDialog as primary login method on Win10+

This commit is contained in:
Mbucari 2023-04-12 10:40:32 -06:00
parent 53b5c1b902
commit df2936e0b6
10 changed files with 208 additions and 14 deletions

View File

@ -0,0 +1,46 @@
namespace LibationWinForms.Login
{
partial class WebLoginDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
SuspendLayout();
//
// WebLoginDialog
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(484, 761);
Name = "WebLoginDialog";
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
Text = "Audible Login";
ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,67 @@
using Dinah.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Windows.Forms;
namespace LibationWinForms.Login
{
public partial class WebLoginDialog : Form
{
public string ResponseUrl { get; private set; }
private readonly string loginUrl;
private readonly string accountID;
private readonly WebView2 webView = new();
public WebLoginDialog()
{
InitializeComponent();
webView.Dock = DockStyle.Fill;
Controls.Add(webView);
Shown += WebLoginDialog_Shown;
this.SetLibationIcon();
}
public WebLoginDialog(string accountID, string loginUrl) : this()
{
this.accountID = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountID, nameof(accountID));
this.loginUrl = ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl));
}
private async void WebLoginDialog_Shown(object sender, EventArgs e)
{
await webView.EnsureCoreWebView2Async();
webView.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting;
webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
webView.CoreWebView2.Navigate(loginUrl);
}
private async void CoreWebView2_DOMContentLoaded(object sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs e)
{
await webView.CoreWebView2.ExecuteScriptAsync(getScript(accountID));
}
private static string getScript(string accountID) => $$"""
(function() {
var inputs = document.getElementsByTagName('input');
for (index = 0; index < inputs.length; ++index) {
if (inputs[index].name.includes('email')) {
inputs[index].value = '{{accountID}}';
}
if (inputs[index].name.includes('password')) {
inputs[index].focus();
}
}
})()
""";
private void CoreWebView2_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
{
if (new Uri(e.Uri).AbsolutePath.Contains("/ap/maplanding"))
{
ResponseUrl = e.Uri;
e.Cancel = true;
DialogResult = DialogResult.OK;
Close();
}
}
}
}

View File

@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,15 +1,22 @@
using System; using System;
using System.Windows.Forms;
namespace LibationWinForms.Dialogs.Login namespace LibationWinForms.Dialogs.Login
{ {
public abstract class WinformLoginBase public abstract class WinformLoginBase
{ {
/// <returns>True if ShowDialog's DialogResult == OK</returns> private readonly IWin32Window _owner;
protected static bool ShowDialog(System.Windows.Forms.Form dialog) protected WinformLoginBase(IWin32Window owner)
{ {
var result = dialog.ShowDialog(); _owner = owner;
}
/// <returns>True if ShowDialog's DialogResult == OK</returns>
protected bool ShowDialog(Form dialog)
{
var result = dialog.ShowDialog(_owner);
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result }); Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
return result == System.Windows.Forms.DialogResult.OK; return result == DialogResult.OK;
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms;
using AudibleApi; using AudibleApi;
using AudibleUtilities; using AudibleUtilities;
using LibationWinForms.Dialogs.Login; using LibationWinForms.Dialogs.Login;
@ -12,7 +13,7 @@ namespace LibationWinForms.Login
public string DeviceName { get; } = "Libation"; public string DeviceName { get; } = "Libation";
public WinformLoginCallback(Account account) public WinformLoginCallback(Account account, IWin32Window owner) : base(owner)
{ {
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms;
using AudibleApi; using AudibleApi;
using AudibleUtilities; using AudibleUtilities;
using LibationWinForms.Dialogs.Login; using LibationWinForms.Dialogs.Login;
@ -9,20 +10,31 @@ namespace LibationWinForms.Login
public class WinformLoginChoiceEager : WinformLoginBase, ILoginChoiceEager public class WinformLoginChoiceEager : WinformLoginBase, ILoginChoiceEager
{ {
/// <summary>Convenience method. Recommended when wiring up Winforms to <see cref="ApplicationServices.LibraryCommands.ImportAccountAsync"/></summary> /// <summary>Convenience method. Recommended when wiring up Winforms to <see cref="ApplicationServices.LibraryCommands.ImportAccountAsync"/></summary>
public static async Task<ApiExtended> ApiExtendedFunc(Account account) => await ApiExtended.CreateAsync(account, new WinformLoginChoiceEager(account)); public static Func<Account, Task<ApiExtended>> CreateApiExtendedFunc(IWin32Window owner) => a => ApiExtendedFunc(a, owner);
private static async Task<ApiExtended> ApiExtendedFunc(Account account, IWin32Window owner)
=> await ApiExtended.CreateAsync(account, new WinformLoginChoiceEager(account, owner));
public ILoginCallback LoginCallback { get; private set; } public ILoginCallback LoginCallback { get; private set; }
private Account _account { get; } private Account _account { get; }
public WinformLoginChoiceEager(Account account) private WinformLoginChoiceEager(Account account, IWin32Window owner) : base(owner)
{ {
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
LoginCallback = new WinformLoginCallback(_account); LoginCallback = new WinformLoginCallback(_account, owner);
} }
public Task<ChoiceOut> StartAsync(ChoiceIn choiceIn) public Task<ChoiceOut> StartAsync(ChoiceIn choiceIn)
{ {
if (Environment.OSVersion.Version.Major >= 10)
{
using var browserLogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
if (ShowDialog(browserLogin))
return Task.FromResult(ChoiceOut.External(browserLogin.ResponseUrl));
}
using var dialog = new LoginChoiceEagerDialog(_account); using var dialog = new LoginChoiceEagerDialog(_account);
if (!ShowDialog(dialog) || (dialog.LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(dialog.Password))) if (!ShowDialog(dialog) || (dialog.LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(dialog.Password)))

View File

@ -32,7 +32,7 @@ namespace LibationWinForms
// in autoScan, new books SHALL NOT show dialog // in autoScan, new books SHALL NOT show dialog
try try
{ {
Task importAsync() => LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts); Task importAsync() => LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.CreateApiExtendedFunc(this), accounts);
if (InvokeRequired) if (InvokeRequired)
await Invoke(importAsync); await Invoke(importAsync);
else else

View File

@ -74,7 +74,7 @@ namespace LibationWinForms
{ {
try try
{ {
var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts); var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.CreateApiExtendedFunc(this), accounts);
// this is here instead of ScanEnd so that the following is only possible when it's user-initiated, not automatic loop // this is here instead of ScanEnd so that the following is only possible when it's user-initiated, not automatic loop
if (Configuration.Instance.ShowImportedStats && newAdded > 0) if (Configuration.Instance.ShowImportedStats && newAdded > 0)

View File

@ -279,7 +279,7 @@ namespace LibationWinForms.GridView
.Select(lbe => lbe.LibraryBook) .Select(lbe => lbe.LibraryBook)
.Where(lb => !lb.Book.HasLiberated()); .Where(lb => !lb.Book.HasLiberated());
var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.ApiExtendedFunc, lib, accounts); var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.CreateApiExtendedFunc(this), lib, accounts);
var removable = allBooks.Where(lbe => removedBooks.Any(rb => rb.Book.AudibleProductId == lbe.AudibleProductId)).ToList(); var removable = allBooks.Where(lbe => removedBooks.Any(rb => rb.Book.AudibleProductId == lbe.AudibleProductId)).ToList();

View File

@ -38,6 +38,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" /> <PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1777-prerelease" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>