Use WebLoginDialog as primary login method on Win10+
This commit is contained in:
parent
53b5c1b902
commit
df2936e0b6
46
Source/LibationWinForms/Dialogs/Login/WebLoginDialog.Designer.cs
generated
Normal file
46
Source/LibationWinForms/Dialogs/Login/WebLoginDialog.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs
Normal file
67
Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Source/LibationWinForms/Dialogs/Login/WebLoginDialog.resx
Normal file
60
Source/LibationWinForms/Dialogs/Login/WebLoginDialog.resx
Normal 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>
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)))
|
||||||
@ -33,13 +45,13 @@ namespace LibationWinForms.Login
|
|||||||
case LoginMethod.Api:
|
case LoginMethod.Api:
|
||||||
return Task.FromResult(ChoiceOut.WithApi(dialog.Email, dialog.Password));
|
return Task.FromResult(ChoiceOut.WithApi(dialog.Email, dialog.Password));
|
||||||
case LoginMethod.External:
|
case LoginMethod.External:
|
||||||
{
|
{
|
||||||
using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
||||||
return Task.FromResult(
|
return Task.FromResult(
|
||||||
ShowDialog(externalDialog)
|
ShowDialog(externalDialog)
|
||||||
? ChoiceOut.External(externalDialog.ResponseUrl)
|
? ChoiceOut.External(externalDialog.ResponseUrl)
|
||||||
: null);
|
: null);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Exception($"Unknown {nameof(LoginMethod)} value");
|
throw new Exception($"Unknown {nameof(LoginMethod)} value");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user